Merge pull request #10204 from murgatroid99/node_protobuf_js_6_upgrade

Upgrade Node dependency on Protobuf.js to version 6
diff --git a/package.json b/package.json
index 666e819..7f24232 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
     "lodash": "^4.15.0",
     "nan": "^2.0.0",
     "node-pre-gyp": "^0.6.0",
-    "protobufjs": "^5.0.0",
+    "protobufjs": "^6.7.0",
     "cares": "^1.1.5"
   },
   "devDependencies": {
diff --git a/src/node/index.js b/src/node/index.js
index a294aad..071bfd7 100644
--- a/src/node/index.js
+++ b/src/node/index.js
@@ -52,42 +52,89 @@
 
 var grpc = require('./src/grpc_extension');
 
+var protobuf_js_5_common = require('./src/protobuf_js_5_common');
+var protobuf_js_6_common = require('./src/protobuf_js_6_common');
+
 grpc.setDefaultRootsPem(fs.readFileSync(SSL_ROOTS_PATH, 'ascii'));
 
 /**
- * Load a gRPC object from an existing ProtoBuf.Reflect object.
- * @param {ProtoBuf.Reflect.Namespace} value The ProtoBuf object to load.
- * @param {Object=} options Options to apply to the loaded object
+ * Load a ProtoBuf.js object as a gRPC object. The options object can provide
+ * the following options:
+ * - binaryAsBase64: deserialize bytes values as base64 strings instead of
+ *   Buffers. Defaults to false
+ * - longsAsStrings: deserialize long values as strings instead of objects.
+ *   Defaults to true
+ * - enumsAsStrings: deserialize enum values as strings instead of numbers.
+ *   Defaults to true
+ * - deprecatedArgumentOrder: Use the beta method argument order for client
+ *   methods, with optional arguments after the callback. Defaults to false.
+ *   This option is only a temporary stopgap measure to smooth an API breakage.
+ *   It is deprecated, and new code should not use it.
+ * - protobufjsVersion: Available values are 5, 6, and 'detect'. 5 and 6
+ *   respectively indicate that an object from the corresponding version of
+ *   ProtoBuf.js is provided in the value argument. If the option is 'detect',
+ *   gRPC will guess what the version is based on the structure of the value.
+ *   Defaults to 'detect'.
+ * @param {Object} value The ProtoBuf.js reflection object to load
+ * @param {Object=} options Options to apply to the loaded file
  * @return {Object<string, *>} The resulting gRPC object
  */
 exports.loadObject = function loadObject(value, options) {
-  var result = {};
-  if (value.className === 'Namespace') {
-    _.each(value.children, function(child) {
-      result[child.name] = loadObject(child, options);
-    });
-    return result;
-  } else if (value.className === 'Service') {
-    return client.makeProtobufClientConstructor(value, options);
-  } else if (value.className === 'Message' || value.className === 'Enum') {
-    return value.build();
+  options = _.defaults(options, common.defaultGrpcOptions);
+  options = _.defaults(options, {'protobufjsVersion': 'detect'});
+  var protobufjsVersion;
+  if (options.protobufjsVersion === 'detect') {
+    if (protobuf_js_6_common.isProbablyProtobufJs6(value)) {
+      protobufjsVersion = 6;
+    } else if (protobuf_js_5_common.isProbablyProtobufJs5(value)) {
+      protobufjsVersion = 5;
+    } else {
+      var error_message = 'Could not detect ProtoBuf.js version. Please ' +
+          'specify the version number with the "protobufjs_version" option';
+      throw new Error(error_message);
+    }
   } else {
-    return value;
+    protobufjsVersion = options.protobufjsVersion;
+  }
+  switch (protobufjsVersion) {
+    case 6: return protobuf_js_6_common.loadObject(value, options);
+    case 5:
+    var deprecation_message = 'Calling grpc.loadObject with an object ' +
+        'generated by ProtoBuf.js 5 is deprecated. Please upgrade to ' +
+        'ProtoBuf.js 6.';
+    common.log(grpc.logVerbosity.INFO, deprecation_message);
+    return protobuf_js_5_common.loadObject(value, options);
+    default:
+    throw new Error('Unrecognized protobufjsVersion', protobufjsVersion);
   }
 };
 
 var loadObject = exports.loadObject;
 
+function applyProtoRoot(filename, root) {
+  if (_.isString(filename)) {
+    return filename;
+  }
+  filename.root = path.resolve(filename.root) + '/';
+  root.resolvePath = function(originPath, importPath, alreadyNormalized) {
+    return ProtoBuf.util.path.resolve(filename.root,
+                                      importPath,
+                                      alreadyNormalized);
+  };
+  return filename.file;
+}
+
 /**
  * Load a gRPC object from a .proto file. The options object can provide the
  * following options:
- * - convertFieldsToCamelCase: Loads this file with that option on protobuf.js
- *   set as specified. See
- *   https://github.com/dcodeIO/protobuf.js/wiki/Advanced-options for details
+ * - convertFieldsToCamelCase: Load this file with field names in camel case
+ *   instead of their original case
  * - binaryAsBase64: deserialize bytes values as base64 strings instead of
  *   Buffers. Defaults to false
  * - longsAsStrings: deserialize long values as strings instead of objects.
  *   Defaults to true
+ * - enumsAsStrings: deserialize enum values as strings instead of numbers.
+ *   Defaults to true
  * - deprecatedArgumentOrder: Use the beta method argument order for client
  *   methods, with optional arguments after the callback. Defaults to false.
  *   This option is only a temporary stopgap measure to smooth an API breakage.
@@ -99,29 +146,17 @@
  * @return {Object<string, *>} The resulting gRPC object
  */
 exports.load = function load(filename, format, options) {
-  if (!format) {
-    format = 'proto';
-  }
-  var convertFieldsToCamelCaseOriginal = ProtoBuf.convertFieldsToCamelCase;
-  if(options && options.hasOwnProperty('convertFieldsToCamelCase')) {
-    ProtoBuf.convertFieldsToCamelCase = options.convertFieldsToCamelCase;
-  }
-  var builder;
-  try {
-    switch(format) {
-      case 'proto':
-      builder = ProtoBuf.loadProtoFile(filename);
-      break;
-      case 'json':
-      builder = ProtoBuf.loadJsonFile(filename);
-      break;
-      default:
-      throw new Error('Unrecognized format "' + format + '"');
-    }
-  } finally {
-    ProtoBuf.convertFieldsToCamelCase = convertFieldsToCamelCaseOriginal;
-  }
-  return loadObject(builder.ns, options);
+  /* Note: format is currently unused, because the API for loading a proto
+     file or a JSON file is identical in Protobuf.js 6. In the future, there is
+     still the possibility of adding other formats that would be loaded
+     differently */
+  options = _.defaults(options, common.defaultGrpcOptions);
+  options.protobufjs_version = 6;
+  var root = new ProtoBuf.Root();
+  var parse_options = {keepCase: !options.convertFieldsToCamelCase};
+  return loadObject(root.loadSync(applyProtoRoot(filename, root),
+                                  parse_options),
+                    options);
 };
 
 var log_template = _.template(
diff --git a/src/node/interop/interop_server.js b/src/node/interop/interop_server.js
index 05f52a1..83b8a7c 100644
--- a/src/node/interop/interop_server.js
+++ b/src/node/interop/interop_server.js
@@ -228,7 +228,7 @@
     server_creds = grpc.ServerCredentials.createInsecure();
   }
   var server = new grpc.Server(options);
-  server.addProtoService(testProto.TestService.service, {
+  server.addService(testProto.TestService.service, {
     emptyCall: handleEmpty,
     unaryCall: handleUnary,
     streamingOutputCall: handleStreamingOutput,
diff --git a/src/node/performance/benchmark_server.js b/src/node/performance/benchmark_server.js
index 7158af7..ea85029 100644
--- a/src/node/performance/benchmark_server.js
+++ b/src/node/performance/benchmark_server.js
@@ -140,7 +140,7 @@
       streamingCall: makeStreamingGenericCall(response_size)
     });
   } else {
-    server.addProtoService(serviceProto.BenchmarkService.service, {
+    server.addService(serviceProto.BenchmarkService.service, {
       unaryCall: unaryCall,
       streamingCall: streamingCall
     });
diff --git a/src/node/performance/worker.js b/src/node/performance/worker.js
index 030bf7d..90a9b7d 100644
--- a/src/node/performance/worker.js
+++ b/src/node/performance/worker.js
@@ -44,8 +44,8 @@
 function runServer(port, benchmark_impl) {
   var server_creds = grpc.ServerCredentials.createInsecure();
   var server = new grpc.Server();
-  server.addProtoService(serviceProto.WorkerService.service,
-                         new WorkerServiceImpl(benchmark_impl, server));
+  server.addService(serviceProto.WorkerService.service,
+                    new WorkerServiceImpl(benchmark_impl, server));
   var address = '0.0.0.0:' + port;
   server.bind(address, server_creds);
   server.start();
diff --git a/src/node/src/client.js b/src/node/src/client.js
index 44081a3..1aaf35c 100644
--- a/src/node/src/client.js
+++ b/src/node/src/client.js
@@ -780,6 +780,8 @@
     _.assign(Client.prototype[name], attrs);
   });
 
+  Client.service = methods;
+
   return Client;
 };
 
@@ -823,26 +825,6 @@
 };
 
 /**
- * Creates a constructor for clients for the given service
- * @param {ProtoBuf.Reflect.Service} service The service to generate a client
- *     for
- * @param {Object=} options Options to apply to the client
- * @return {function(string, Object)} New client constructor
- */
-exports.makeProtobufClientConstructor =  function(service, options) {
-  var method_attrs = common.getProtobufServiceAttrs(service, options);
-  if (!options) {
-    options = {deprecatedArgumentOrder: false};
-  }
-  var Client = exports.makeClientConstructor(
-      method_attrs, common.fullyQualifiedName(service),
-      options);
-  Client.service = service;
-  Client.service.grpc_options = options;
-  return Client;
-};
-
-/**
  * Map of status code names to status codes
  */
 exports.status = grpc.status;
diff --git a/src/node/src/common.js b/src/node/src/common.js
index a0fe448..757969d 100644
--- a/src/node/src/common.js
+++ b/src/node/src/common.js
@@ -42,74 +42,6 @@
 var _ = require('lodash');
 
 /**
- * Get a function that deserializes a specific type of protobuf.
- * @param {function()} cls The constructor of the message type to deserialize
- * @param {bool=} binaryAsBase64 Deserialize bytes fields as base64 strings
- *     instead of Buffers. Defaults to false
- * @param {bool=} longsAsStrings Deserialize long values as strings instead of
- *     objects. Defaults to true
- * @return {function(Buffer):cls} The deserialization function
- */
-exports.deserializeCls = function deserializeCls(cls, binaryAsBase64,
-                                                 longsAsStrings) {
-  if (binaryAsBase64 === undefined || binaryAsBase64 === null) {
-    binaryAsBase64 = false;
-  }
-  if (longsAsStrings === undefined || longsAsStrings === null) {
-    longsAsStrings = true;
-  }
-  /**
-   * Deserialize a buffer to a message object
-   * @param {Buffer} arg_buf The buffer to deserialize
-   * @return {cls} The resulting object
-   */
-  return function deserialize(arg_buf) {
-    // Convert to a native object with binary fields as Buffers (first argument)
-    // and longs as strings (second argument)
-    return cls.decode(arg_buf).toRaw(binaryAsBase64, longsAsStrings);
-  };
-};
-
-var deserializeCls = exports.deserializeCls;
-
-/**
- * Get a function that serializes objects to a buffer by protobuf class.
- * @param {function()} Cls The constructor of the message type to serialize
- * @return {function(Cls):Buffer} The serialization function
- */
-exports.serializeCls = function serializeCls(Cls) {
-  /**
-   * Serialize an object to a Buffer
-   * @param {Object} arg The object to serialize
-   * @return {Buffer} The serialized object
-   */
-  return function serialize(arg) {
-    return new Buffer(new Cls(arg).encode().toBuffer());
-  };
-};
-
-var serializeCls = exports.serializeCls;
-
-/**
- * Get the fully qualified (dotted) name of a ProtoBuf.Reflect value.
- * @param {ProtoBuf.Reflect.Namespace} value The value to get the name of
- * @return {string} The fully qualified name of the value
- */
-exports.fullyQualifiedName = function fullyQualifiedName(value) {
-  if (value === null || value === undefined) {
-    return '';
-  }
-  var name = value.name;
-  var parent_name = fullyQualifiedName(value.parent);
-  if (parent_name !== '') {
-    name = parent_name + '.' + name;
-  }
-  return name;
-};
-
-var fullyQualifiedName = exports.fullyQualifiedName;
-
-/**
  * Wrap a function to pass null-like values through without calling it. If no
  * function is given, just uses the identity;
  * @param {?function} func The function to wrap
@@ -128,44 +60,6 @@
 };
 
 /**
- * Return a map from method names to method attributes for the service.
- * @param {ProtoBuf.Reflect.Service} service The service to get attributes for
- * @param {Object=} options Options to apply to these attributes
- * @return {Object} The attributes map
- */
-exports.getProtobufServiceAttrs = function getProtobufServiceAttrs(service,
-                                                                   options) {
-  var prefix = '/' + fullyQualifiedName(service) + '/';
-  var binaryAsBase64, longsAsStrings;
-  if (options) {
-    binaryAsBase64 = options.binaryAsBase64;
-    longsAsStrings = options.longsAsStrings;
-  }
-  /* This slightly awkward construction is used to make sure we only use
-     lodash@3.10.1-compatible functions. A previous version used
-     _.fromPairs, which would be cleaner, but was introduced in lodash
-     version 4 */
-  return _.zipObject(_.map(service.children, function(method) {
-    return _.camelCase(method.name);
-  }), _.map(service.children, function(method) {
-    return {
-      originalName: method.name,
-      path: prefix + method.name,
-      requestStream: method.requestStream,
-      responseStream: method.responseStream,
-      requestType: method.resolvedRequestType,
-      responseType: method.resolvedResponseType,
-      requestSerialize: serializeCls(method.resolvedRequestType.build()),
-      requestDeserialize: deserializeCls(method.resolvedRequestType.build(),
-                                         binaryAsBase64, longsAsStrings),
-      responseSerialize: serializeCls(method.resolvedResponseType.build()),
-      responseDeserialize: deserializeCls(method.resolvedResponseType.build(),
-                                          binaryAsBase64, longsAsStrings)
-    };
-  }));
-};
-
-/**
  * The logger object for the gRPC module. Defaults to console.
  */
 exports.logger = console;
@@ -185,3 +79,14 @@
     exports.logger.error(message);
   }
 };
+
+/**
+ * Default options for loading proto files into gRPC
+ */
+exports.defaultGrpcOptions = {
+  convertFieldsToCamelCase: false,
+  binaryAsBase64: false,
+  longsAsStrings: true,
+  enumsAsStrings: true,
+  deprecatedArgumentOrder: false
+};
diff --git a/src/node/src/protobuf_js_5_common.js b/src/node/src/protobuf_js_5_common.js
new file mode 100644
index 0000000..62cf2f4
--- /dev/null
+++ b/src/node/src/protobuf_js_5_common.js
@@ -0,0 +1,181 @@
+/*
+ *
+ * Copyright 2017, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+'use strict';
+
+var _ = require('lodash');
+var client = require('./client');
+
+/**
+ * Get a function that deserializes a specific type of protobuf.
+ * @param {function()} cls The constructor of the message type to deserialize
+ * @param {bool=} binaryAsBase64 Deserialize bytes fields as base64 strings
+ *     instead of Buffers. Defaults to false
+ * @param {bool=} longsAsStrings Deserialize long values as strings instead of
+ *     objects. Defaults to true
+ * @return {function(Buffer):cls} The deserialization function
+ */
+exports.deserializeCls = function deserializeCls(cls, binaryAsBase64,
+                                                 longsAsStrings) {
+  /**
+   * Deserialize a buffer to a message object
+   * @param {Buffer} arg_buf The buffer to deserialize
+   * @return {cls} The resulting object
+   */
+  return function deserialize(arg_buf) {
+    // Convert to a native object with binary fields as Buffers (first argument)
+    // and longs as strings (second argument)
+    return cls.decode(arg_buf).toRaw(binaryAsBase64, longsAsStrings);
+  };
+};
+
+var deserializeCls = exports.deserializeCls;
+
+/**
+ * Get a function that serializes objects to a buffer by protobuf class.
+ * @param {function()} Cls The constructor of the message type to serialize
+ * @return {function(Cls):Buffer} The serialization function
+ */
+exports.serializeCls = function serializeCls(Cls) {
+  /**
+   * Serialize an object to a Buffer
+   * @param {Object} arg The object to serialize
+   * @return {Buffer} The serialized object
+   */
+  return function serialize(arg) {
+    return new Buffer(new Cls(arg).encode().toBuffer());
+  };
+};
+
+var serializeCls = exports.serializeCls;
+
+/**
+ * Get the fully qualified (dotted) name of a ProtoBuf.Reflect value.
+ * @param {ProtoBuf.Reflect.Namespace} value The value to get the name of
+ * @return {string} The fully qualified name of the value
+ */
+exports.fullyQualifiedName = function fullyQualifiedName(value) {
+  if (value === null || value === undefined) {
+    return '';
+  }
+  var name = value.name;
+  var parent_name = fullyQualifiedName(value.parent);
+  if (parent_name !== '') {
+    name = parent_name + '.' + name;
+  }
+  return name;
+};
+
+var fullyQualifiedName = exports.fullyQualifiedName;
+
+/**
+ * Return a map from method names to method attributes for the service.
+ * @param {ProtoBuf.Reflect.Service} service The service to get attributes for
+ * @param {Object=} options Options to apply to these attributes
+ * @return {Object} The attributes map
+ */
+exports.getProtobufServiceAttrs = function getProtobufServiceAttrs(service,
+                                                                   options) {
+  var prefix = '/' + fullyQualifiedName(service) + '/';
+  var binaryAsBase64, longsAsStrings;
+  if (options) {
+    binaryAsBase64 = options.binaryAsBase64;
+    longsAsStrings = options.longsAsStrings;
+  }
+  /* This slightly awkward construction is used to make sure we only use
+     lodash@3.10.1-compatible functions. A previous version used
+     _.fromPairs, which would be cleaner, but was introduced in lodash
+     version 4 */
+  return _.zipObject(_.map(service.children, function(method) {
+    return _.camelCase(method.name);
+  }), _.map(service.children, function(method) {
+    return {
+      originalName: method.name,
+      path: prefix + method.name,
+      requestStream: method.requestStream,
+      responseStream: method.responseStream,
+      requestType: method.resolvedRequestType,
+      responseType: method.resolvedResponseType,
+      requestSerialize: serializeCls(method.resolvedRequestType.build()),
+      requestDeserialize: deserializeCls(method.resolvedRequestType.build(),
+                                         binaryAsBase64, longsAsStrings),
+      responseSerialize: serializeCls(method.resolvedResponseType.build()),
+      responseDeserialize: deserializeCls(method.resolvedResponseType.build(),
+                                          binaryAsBase64, longsAsStrings)
+    };
+  }));
+};
+
+var getProtobufServiceAttrs = exports.getProtobufServiceAttrs;
+
+/**
+ * Load a gRPC object from an existing ProtoBuf.Reflect object.
+ * @param {ProtoBuf.Reflect.Namespace} value The ProtoBuf object to load.
+ * @param {Object=} options Options to apply to the loaded object
+ * @return {Object<string, *>} The resulting gRPC object
+ */
+exports.loadObject = function loadObject(value, options) {
+  var result = {};
+  if (!value) {
+    return value;
+  }
+  if (value.hasOwnProperty('ns')) {
+    return loadObject(value.ns, options);
+  }
+  if (value.className === 'Namespace') {
+    _.each(value.children, function(child) {
+      result[child.name] = loadObject(child, options);
+    });
+    return result;
+  } else if (value.className === 'Service') {
+    return client.makeClientConstructor(getProtobufServiceAttrs(value, options),
+                                        options);
+  } else if (value.className === 'Message' || value.className === 'Enum') {
+    return value.build();
+  } else {
+    return value;
+  }
+};
+
+/**
+ * The primary purpose of this method is to distinguish between reflection
+ * objects from different versions of ProtoBuf.js. This is just a heuristic,
+ * checking for properties that are (currently) specific to this version of
+ * ProtoBuf.js
+ * @param {Object} obj The object to check
+ * @return {boolean} Whether the object appears to be a Protobuf.js 5
+ *   ReflectionObject
+ */
+exports.isProbablyProtobufJs5 = function isProbablyProtobufJs5(obj) {
+  return _.isArray(obj.children) && (typeof obj.build === 'function');
+};
diff --git a/src/node/src/protobuf_js_6_common.js b/src/node/src/protobuf_js_6_common.js
new file mode 100644
index 0000000..00f11f2
--- /dev/null
+++ b/src/node/src/protobuf_js_6_common.js
@@ -0,0 +1,170 @@
+/*
+ *
+ * Copyright 2017, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+'use strict';
+
+var _ = require('lodash');
+var client = require('./client');
+
+/**
+ * Get a function that deserializes a specific type of protobuf.
+ * @param {function()} cls The constructor of the message type to deserialize
+ * @param {bool=} binaryAsBase64 Deserialize bytes fields as base64 strings
+ *     instead of Buffers. Defaults to false
+ * @param {bool=} longsAsStrings Deserialize long values as strings instead of
+ *     objects. Defaults to true
+ * @return {function(Buffer):cls} The deserialization function
+ */
+exports.deserializeCls = function deserializeCls(cls, options) {
+  var conversion_options = {
+    defaults: true,
+    bytes: options.binaryAsBase64 ? String : Buffer,
+    longs: options.longsAsStrings ? String : null,
+    enums: options.enumsAsStrings ? String : null,
+    oneofs: true
+  };
+  /**
+   * Deserialize a buffer to a message object
+   * @param {Buffer} arg_buf The buffer to deserialize
+   * @return {cls} The resulting object
+   */
+  return function deserialize(arg_buf) {
+    return cls.decode(arg_buf).toObject(conversion_options);
+  };
+};
+
+var deserializeCls = exports.deserializeCls;
+
+/**
+ * Get a function that serializes objects to a buffer by protobuf class.
+ * @param {function()} Cls The constructor of the message type to serialize
+ * @return {function(Cls):Buffer} The serialization function
+ */
+exports.serializeCls = function serializeCls(cls) {
+  /**
+   * Serialize an object to a Buffer
+   * @param {Object} arg The object to serialize
+   * @return {Buffer} The serialized object
+   */
+  return function serialize(arg) {
+    var message = cls.fromObject(arg);
+    return cls.encode(message).finish();
+  };
+};
+
+var serializeCls = exports.serializeCls;
+
+/**
+ * Get the fully qualified (dotted) name of a ProtoBuf.Reflect value.
+ * @param {ProtoBuf.ReflectionObject} value The value to get the name of
+ * @return {string} The fully qualified name of the value
+ */
+exports.fullyQualifiedName = function fullyQualifiedName(value) {
+  if (value === null || value === undefined) {
+    return '';
+  }
+  var name = value.name;
+  var parent_fqn = fullyQualifiedName(value.parent);
+  if (parent_fqn !== '') {
+    name = parent_fqn + '.' + name;
+  }
+  return name;
+};
+
+var fullyQualifiedName = exports.fullyQualifiedName;
+
+/**
+ * Return a map from method names to method attributes for the service.
+ * @param {ProtoBuf.Service} service The service to get attributes for
+ * @param {Object=} options Options to apply to these attributes
+ * @return {Object} The attributes map
+ */
+exports.getProtobufServiceAttrs = function getProtobufServiceAttrs(service,
+                                                                   options) {
+  var prefix = '/' + fullyQualifiedName(service) + '/';
+  service.resolveAll();
+  return _.zipObject(_.map(service.methods, function(method) {
+    return _.camelCase(method.name);
+  }), _.map(service.methods, function(method) {
+    return {
+      originalName: method.name,
+      path: prefix + method.name,
+      requestStream: !!method.requestStream,
+      responseStream: !!method.responseStream,
+      requestType: method.resolvedRequestType,
+      responseType: method.resolvedResponseType,
+      requestSerialize: serializeCls(method.resolvedRequestType),
+      requestDeserialize: deserializeCls(method.resolvedRequestType, options),
+      responseSerialize: serializeCls(method.resolvedResponseType),
+      responseDeserialize: deserializeCls(method.resolvedResponseType, options)
+    };
+  }));
+};
+
+var getProtobufServiceAttrs = exports.getProtobufServiceAttrs;
+
+exports.loadObject = function loadObject(value, options) {
+  var result = {};
+  if (!value) {
+    return value;
+  }
+  if (value.hasOwnProperty('methods')) {
+    // It's a service object
+    var service_attrs = getProtobufServiceAttrs(value, options);
+    return client.makeClientConstructor(service_attrs);
+  }
+
+  if (value.hasOwnProperty('nested')) {
+    // It's a namespace or root object
+    _.each(value.nested, function(nested, name) {
+      result[name] = loadObject(nested, options);
+    });
+    return result;
+  }
+
+  // Otherwise, it's not something we need to change
+  return value;
+};
+
+/**
+ * The primary purpose of this method is to distinguish between reflection
+ * objects from different versions of ProtoBuf.js. This is just a heuristic,
+ * checking for properties that are (currently) specific to this version of
+ * ProtoBuf.js
+ * @param {Object} obj The object to check
+ * @return {boolean} Whether the object appears to be a Protobuf.js 6
+ *   ReflectionObject
+ */
+exports.isProbablyProtobufJs6 = function isProbablyProtobufJs6(obj) {
+  return (typeof obj.root === 'object') && (typeof obj.resolve === 'function');
+};
diff --git a/src/node/src/server.js b/src/node/src/server.js
index bdb4a56..3450abe 100644
--- a/src/node/src/server.js
+++ b/src/node/src/server.js
@@ -781,17 +781,31 @@
 
 /**
  * Add a proto service to the server, with a corresponding implementation
+ * @deprecated Use grpc.load and Server#addService instead
  * @param {Protobuf.Reflect.Service} service The proto service descriptor
  * @param {Object<String, function>} implementation Map of method names to
  *     method implementation for the provided service.
  */
 Server.prototype.addProtoService = function(service, implementation) {
   var options;
-  if (service.grpc_options) {
-    options = service.grpc_options;
+  var protobuf_js_5_common = require('./protobuf_js_5_common');
+  var protobuf_js_6_common = require('./protobuf_js_6_common');
+  common.log(grpc.logVerbosity.INFO,
+             'Server#addProtoService is deprecated. Use addService instead');
+  if (protobuf_js_5_common.isProbablyProtobufJs5(service)) {
+    options = _.defaults(service.grpc_options, common.defaultGrpcOptions);
+    this.addService(
+        protobuf_js_5_common.getProtobufServiceAttrs(service, options),
+        implementation);
+  } else if (protobuf_js_6_common.isProbablyProtobufJs6(service)) {
+    options = _.defaults(service.grpc_options, common.defaultGrpcOptions);
+    this.addService(
+        protobuf_js_6_common.getProtobufServiceAttrs(service, options),
+        implementation);
+  } else {
+    // We assume that this is a service attributes object
+    this.addService(service, implementation);
   }
-  this.addService(common.getProtobufServiceAttrs(service, options),
-                  implementation);
 };
 
 /**
diff --git a/src/node/stress/metrics_server.js b/src/node/stress/metrics_server.js
index 3ab4b4c..b3f939e 100644
--- a/src/node/stress/metrics_server.js
+++ b/src/node/stress/metrics_server.js
@@ -63,7 +63,7 @@
 
 function MetricsServer(port) {
   var server = new grpc.Server();
-  server.addProtoService(metrics.MetricsService.service, {
+  server.addService(metrics.MetricsService.service, {
     getGauge: _.bind(getGauge, this),
     getAllGauges: _.bind(getAllGauges, this)
   });
diff --git a/src/node/test/common_test.js b/src/node/test/common_test.js
index c57b738..e1ce864 100644
--- a/src/node/test/common_test.js
+++ b/src/node/test/common_test.js
@@ -34,17 +34,26 @@
 'use strict';
 
 var assert = require('assert');
+var _ = require('lodash');
 
-var common = require('../src/common.js');
+var common = require('../src/common');
+var protobuf_js_6_common = require('../src/protobuf_js_6_common');
+
+var serializeCls = protobuf_js_6_common.serializeCls;
+var deserializeCls = protobuf_js_6_common.deserializeCls;
 
 var ProtoBuf = require('protobufjs');
 
-var messages_proto = ProtoBuf.loadProtoFile(
-    __dirname + '/test_messages.proto').build();
+var messages_proto = new ProtoBuf.Root();
+messages_proto = messages_proto.loadSync(
+    __dirname + '/test_messages.proto', {keepCase: true}).resolveAll();
+
+var default_options = common.defaultGrpcOptions;
 
 describe('Proto message long int serialize and deserialize', function() {
-  var longSerialize = common.serializeCls(messages_proto.LongValues);
-  var longDeserialize = common.deserializeCls(messages_proto.LongValues);
+  var longSerialize = serializeCls(messages_proto.LongValues);
+  var longDeserialize = deserializeCls(messages_proto.LongValues,
+                                       default_options);
   var pos_value = '314159265358979';
   var neg_value = '-27182818284590';
   it('should preserve positive int64 values', function() {
@@ -88,8 +97,9 @@
                        neg_value);
   });
   it('should deserialize as a number with the right option set', function() {
-    var longNumDeserialize = common.deserializeCls(messages_proto.LongValues,
-                                                   false, false);
+    var num_options = _.defaults({longsAsStrings: false}, default_options);
+    var longNumDeserialize = deserializeCls(messages_proto.LongValues,
+                                            num_options);
     var serialized = longSerialize({int_64: pos_value});
     assert.strictEqual(typeof longDeserialize(serialized).int_64, 'string');
     /* With the longsAsStrings option disabled, long values are represented as
@@ -98,11 +108,12 @@
   });
 });
 describe('Proto message bytes serialize and deserialize', function() {
-  var sequenceSerialize = common.serializeCls(messages_proto.SequenceValues);
-  var sequenceDeserialize = common.deserializeCls(
-      messages_proto.SequenceValues);
-  var sequenceBase64Deserialize = common.deserializeCls(
-      messages_proto.SequenceValues, true);
+  var sequenceSerialize = serializeCls(messages_proto.SequenceValues);
+  var sequenceDeserialize = deserializeCls(
+      messages_proto.SequenceValues, default_options);
+  var b64_options = _.defaults({binaryAsBase64: true}, default_options);
+  var sequenceBase64Deserialize = deserializeCls(
+      messages_proto.SequenceValues, b64_options);
   var buffer_val = new Buffer([0x69, 0xb7]);
   var base64_val = 'abc=';
   it('should preserve a buffer', function() {
@@ -120,19 +131,73 @@
     var deserialized = sequenceBase64Deserialize(serialized);
     assert.strictEqual(deserialized.bytes_field, base64_val);
   });
-  /* The next two tests are specific tests to verify that issue
-   * https://github.com/grpc/grpc/issues/5174 has been fixed. They are skipped
-   * because they will not pass until a protobuf.js release has been published
-   * with a fix for https://github.com/dcodeIO/protobuf.js/issues/390 */
-  it.skip('should serialize a repeated field as packed by default', function() {
-    var expected_serialize = new Buffer([0x12, 0x01, 0x01, 0x0a]);
+  it('should serialize a repeated field as packed by default', function() {
+    var expected_serialize = new Buffer([0x12, 0x01, 0x0a]);
     var serialized = sequenceSerialize({repeated_field: [10]});
     assert.strictEqual(expected_serialize.compare(serialized), 0);
   });
-  it.skip('should deserialize packed or unpacked repeated', function() {
-    var serialized = new Buffer([0x12, 0x01, 0x01, 0x0a]);
+  it('should deserialize packed or unpacked repeated', function() {
+    var expectedDeserialize = {
+      bytes_field: new Buffer(''),
+      repeated_field: [10]
+    };
+    var packedSerialized = new Buffer([0x12, 0x01, 0x0a]);
+    var unpackedSerialized = new Buffer([0x10, 0x0a]);
+    var packedDeserialized;
+    var unpackedDeserialized;
     assert.doesNotThrow(function() {
-      sequenceDeserialize(serialized);
+      packedDeserialized = sequenceDeserialize(packedSerialized);
     });
+    assert.doesNotThrow(function() {
+      unpackedDeserialized = sequenceDeserialize(unpackedSerialized);
+    });
+    assert.deepEqual(packedDeserialized, expectedDeserialize);
+    assert.deepEqual(unpackedDeserialized, expectedDeserialize);
+  });
+});
+describe('Proto message oneof serialize and deserialize', function() {
+  var oneofSerialize = serializeCls(messages_proto.OneOfValues);
+  var oneofDeserialize = deserializeCls(
+      messages_proto.OneOfValues, default_options);
+  it('Should have idempotent round trips', function() {
+    var test_message = {oneof_choice: 'int_choice', int_choice: 5};
+    var serialized1 = oneofSerialize(test_message);
+    var deserialized1 = oneofDeserialize(serialized1);
+    assert.equal(deserialized1.int_choice, 5);
+    var serialized2 = oneofSerialize(deserialized1);
+    var deserialized2 = oneofDeserialize(serialized2);
+    assert.deepEqual(deserialized1, deserialized2);
+  });
+  it('Should emit a property indicating which field was chosen', function() {
+    var test_message1 = {oneof_choice: 'int_choice', int_choice: 5};
+    var serialized1 = oneofSerialize(test_message1);
+    var deserialized1 = oneofDeserialize(serialized1);
+    assert.equal(deserialized1.oneof_choice, 'int_choice');
+    var test_message2 = {oneof_choice: 'string_choice', string_choice: 'abc'};
+    var serialized2 = oneofSerialize(test_message2);
+    var deserialized2 = oneofDeserialize(serialized2);
+    assert.equal(deserialized2.oneof_choice, 'string_choice');
+  });
+});
+describe('Proto message enum serialize and deserialize', function() {
+  var enumSerialize = serializeCls(messages_proto.EnumValues);
+  var enumDeserialize = deserializeCls(
+      messages_proto.EnumValues, default_options);
+  var enumIntOptions = _.defaults({enumsAsStrings: false}, default_options);
+  var enumIntDeserialize = deserializeCls(
+      messages_proto.EnumValues, enumIntOptions);
+  it('Should accept both names and numbers', function() {
+    var nameSerialized = enumSerialize({enum_value: 'ONE'});
+    var numberSerialized = enumSerialize({enum_value: 1});
+    assert.strictEqual(messages_proto.TestEnum.ONE, 1);
+    assert.deepEqual(enumDeserialize(nameSerialized),
+                     enumDeserialize(numberSerialized));
+  });
+  it('Should deserialize as a string the enumsAsStrings option', function() {
+    var serialized = enumSerialize({enum_value: 'TWO'});
+    var nameDeserialized = enumDeserialize(serialized);
+    var numberDeserialized = enumIntDeserialize(serialized);
+    assert.deepEqual(nameDeserialized, {enum_value: 'TWO'});
+    assert.deepEqual(numberDeserialized, {enum_value: 2});
   });
 });
diff --git a/src/node/test/credentials_test.js b/src/node/test/credentials_test.js
index 305843f..b66b4bf 100644
--- a/src/node/test/credentials_test.js
+++ b/src/node/test/credentials_test.js
@@ -228,7 +228,7 @@
   before(function() {
     var proto = grpc.load(__dirname + '/test_service.proto');
     server = new grpc.Server();
-    server.addProtoService(proto.TestService.service, {
+    server.addService(proto.TestService.service, {
       unary: function(call, cb) {
         call.sendMetadata(call.metadata);
         cb(null, {});
diff --git a/src/node/test/surface_test.js b/src/node/test/surface_test.js
index 1d73956..783028f 100644
--- a/src/node/test/surface_test.js
+++ b/src/node/test/surface_test.js
@@ -34,19 +34,22 @@
 'use strict';
 
 var assert = require('assert');
+var _ = require('lodash');
 
 var surface_client = require('../src/client.js');
+var common = require('../src/common');
 
 var ProtoBuf = require('protobufjs');
 
 var grpc = require('..');
 
-var math_proto = ProtoBuf.loadProtoFile(__dirname +
-    '/../../proto/math/math.proto');
+var math_proto = new ProtoBuf.Root();
+math_proto = math_proto.loadSync(__dirname +
+    '/../../proto/math/math.proto', {keepCase: true});
 
 var mathService = math_proto.lookup('math.Math');
-
-var _ = require('lodash');
+var mathServiceAttrs = grpc.loadObject(
+    mathService, common.defaultGrpcOptions).service;
 
 /**
  * This is used for testing functions with multiple asynchronous calls that
@@ -87,11 +90,6 @@
       grpc.load(__dirname + '/test_service.json', 'json');
     });
   });
-  it('Should fail to load a file with an unknown format', function() {
-    assert.throws(function() {
-      grpc.load(__dirname + '/test_service.proto', 'fake_format');
-    });
-  });
 });
 describe('surface Server', function() {
   var server;
@@ -132,15 +130,40 @@
   afterEach(function() {
     server.forceShutdown();
   });
-  it('Should succeed with a single service', function() {
+  it('Should succeed with a single proto service', function() {
     assert.doesNotThrow(function() {
       server.addProtoService(mathService, dummyImpls);
     });
   });
+  it('Should succeed with a single service attributes object', function() {
+    assert.doesNotThrow(function() {
+      server.addProtoService(mathServiceAttrs, dummyImpls);
+    });
+  });
+});
+describe('Server.prototype.addService', function() {
+  var server;
+  var dummyImpls = {
+    'div': function() {},
+    'divMany': function() {},
+    'fib': function() {},
+    'sum': function() {}
+  };
+  beforeEach(function() {
+    server = new grpc.Server();
+  });
+  afterEach(function() {
+    server.forceShutdown();
+  });
+  it('Should succeed with a single service', function() {
+    assert.doesNotThrow(function() {
+      server.addService(mathServiceAttrs, dummyImpls);
+    });
+  });
   it('Should fail with conflicting method names', function() {
-    server.addProtoService(mathService, dummyImpls);
+    server.addService(mathServiceAttrs, dummyImpls);
     assert.throws(function() {
-      server.addProtoService(mathService, dummyImpls);
+      server.addService(mathServiceAttrs, dummyImpls);
     });
   });
   it('Should allow method names as originally written', function() {
@@ -172,15 +195,15 @@
   it('Should fail if the server has been started', function() {
     server.start();
     assert.throws(function() {
-      server.addProtoService(mathService, dummyImpls);
+      server.addService(mathServiceAttrs, dummyImpls);
     });
   });
   describe('Default handlers', function() {
     var client;
     beforeEach(function() {
-      server.addProtoService(mathService, {});
+      server.addService(mathServiceAttrs, {});
       var port = server.bind('localhost:0', server_insecure_creds);
-      var Client = surface_client.makeProtobufClientConstructor(mathService);
+      var Client = grpc.loadObject(mathService);
       client = new Client('localhost:' + port,
                           grpc.credentials.createInsecure());
       server.start();
@@ -252,7 +275,7 @@
     server = new grpc.Server();
     port = server.bind('localhost:0', grpc.ServerCredentials.createInsecure());
     server.start();
-    Client = surface_client.makeProtobufClientConstructor(mathService);
+    Client = grpc.loadObject(mathService);
   });
   beforeEach(function() {
     client = new Client('localhost:' + port, grpc.credentials.createInsecure());
@@ -309,16 +332,18 @@
   var server;
   var client;
   before(function() {
-    var test_proto = ProtoBuf.loadProtoFile(__dirname + '/echo_service.proto');
+    var test_proto = new ProtoBuf.Root();
+    test_proto = test_proto.loadSync(__dirname + '/echo_service.proto',
+                                         {keepCase: true});
     var echo_service = test_proto.lookup('EchoService');
+    var Client = grpc.loadObject(echo_service);
     server = new grpc.Server();
-    server.addProtoService(echo_service, {
+    server.addService(Client.service, {
       echo: function(call, callback) {
         callback(null, call.request);
       }
     });
     var port = server.bind('localhost:0', server_insecure_creds);
-    var Client = surface_client.makeProtobufClientConstructor(echo_service);
     client = new Client('localhost:' + port, grpc.credentials.createInsecure());
     server.start();
   });
@@ -432,10 +457,13 @@
   var server;
   var metadata;
   before(function() {
-    var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto');
+    var test_proto = new ProtoBuf.Root();
+    test_proto = test_proto.loadSync(__dirname + '/test_service.proto',
+                                         {keepCase: true});
     var test_service = test_proto.lookup('TestService');
+    var Client = grpc.loadObject(test_service);
     server = new grpc.Server();
-    server.addProtoService(test_service, {
+    server.addService(Client.service, {
       unary: function(call, cb) {
         call.sendMetadata(call.metadata);
         cb(null, {});
@@ -460,7 +488,6 @@
       }
     });
     var port = server.bind('localhost:0', server_insecure_creds);
-    var Client = surface_client.makeProtobufClientConstructor(test_service);
     client = new Client('localhost:' + port, grpc.credentials.createInsecure());
     server.start();
     metadata = new grpc.Metadata();
@@ -533,7 +560,9 @@
   var client;
   var badArg = new Buffer([0xFF]);
   before(function() {
-    var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto');
+    var test_proto = new ProtoBuf.Root();
+    test_proto = test_proto.loadSync(__dirname + '/test_service.proto',
+                                         {keepCase: true});
     var test_service = test_proto.lookup('TestService');
     var malformed_test_service = {
       unary: {
@@ -591,7 +620,7 @@
       }
     });
     var port = server.bind('localhost:0', server_insecure_creds);
-    var Client = surface_client.makeProtobufClientConstructor(test_service);
+    var Client = grpc.loadObject(test_service);
     client = new Client('localhost:' + port, grpc.credentials.createInsecure());
     server.start();
   });
@@ -640,7 +669,9 @@
   var client;
   var server;
   before(function() {
-    var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto');
+    var test_proto = new ProtoBuf.Root();
+    test_proto = test_proto.loadSync(__dirname + '/test_service.proto',
+                                         {keepCase: true});
     var test_service = test_proto.lookup('TestService');
     var malformed_test_service = {
       unary: {
@@ -698,7 +729,7 @@
       }
     });
     var port = server.bind('localhost:0', server_insecure_creds);
-    var Client = surface_client.makeProtobufClientConstructor(test_service);
+    var Client = grpc.loadObject(test_service);
     client = new Client('localhost:' + port, grpc.credentials.createInsecure());
     server.start();
   });
@@ -747,12 +778,15 @@
   var server;
   var port;
   before(function() {
-    var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto');
+    var test_proto = new ProtoBuf.Root();
+    test_proto = test_proto.loadSync(__dirname + '/test_service.proto',
+                                         {keepCase: true});
     test_service = test_proto.lookup('TestService');
+    Client = grpc.loadObject(test_service);
     server = new grpc.Server();
     var trailer_metadata = new grpc.Metadata();
     trailer_metadata.add('trailer-present', 'yes');
-    server.addProtoService(test_service, {
+    server.addService(Client.service, {
       unary: function(call, cb) {
         var req = call.request;
         if (req.error) {
@@ -812,7 +846,6 @@
       }
     });
     port = server.bind('localhost:0', server_insecure_creds);
-    Client = surface_client.makeProtobufClientConstructor(test_service);
     client = new Client('localhost:' + port, grpc.credentials.createInsecure());
     server.start();
   });
@@ -1093,17 +1126,19 @@
   var client;
   var server;
   before(function() {
-    var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto');
+    var test_proto = new ProtoBuf.Root();
+    test_proto = test_proto.loadSync(__dirname + '/test_service.proto',
+                                         {keepCase: true});
     test_service = test_proto.lookup('TestService');
     server = new grpc.Server();
-    server.addProtoService(test_service, {
+    Client = grpc.loadObject(test_service);
+    server.addService(Client.service, {
       unary: function(call) {},
       clientStream: function(stream) {},
       serverStream: function(stream) {},
       bidiStream: function(stream) {}
     });
     var port = server.bind('localhost:0', server_insecure_creds);
-    Client = surface_client.makeProtobufClientConstructor(test_service);
     client = new Client('localhost:' + port, grpc.credentials.createInsecure());
     server.start();
   });
@@ -1138,7 +1173,7 @@
         });
         call.cancel();
       };
-      proxy.addProtoService(test_service, proxy_impl);
+      proxy.addService(Client.service, proxy_impl);
       var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
       proxy.start();
       var proxy_client = new Client('localhost:' + proxy_port,
@@ -1160,7 +1195,7 @@
         });
         call.cancel();
       };
-      proxy.addProtoService(test_service, proxy_impl);
+      proxy.addService(Client.service, proxy_impl);
       var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
       proxy.start();
       var proxy_client = new Client('localhost:' + proxy_port,
@@ -1180,7 +1215,7 @@
         });
         call.cancel();
       };
-      proxy.addProtoService(test_service, proxy_impl);
+      proxy.addService(Client.service, proxy_impl);
       var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
       proxy.start();
       var proxy_client = new Client('localhost:' + proxy_port,
@@ -1204,7 +1239,7 @@
         });
         call.cancel();
       };
-      proxy.addProtoService(test_service, proxy_impl);
+      proxy.addService(Client.service, proxy_impl);
       var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
       proxy.start();
       var proxy_client = new Client('localhost:' + proxy_port,
@@ -1235,7 +1270,7 @@
           }
         });
       };
-      proxy.addProtoService(test_service, proxy_impl);
+      proxy.addService(Client.service, proxy_impl);
       var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
       proxy.start();
       var proxy_client = new Client('localhost:' + proxy_port,
@@ -1259,7 +1294,7 @@
           done();
         });
       };
-      proxy.addProtoService(test_service, proxy_impl);
+      proxy.addService(Client.service, proxy_impl);
       var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
       proxy.start();
       var proxy_client = new Client('localhost:' + proxy_port,
@@ -1279,14 +1314,14 @@
   var server;
   before(function() {
     server = new grpc.Server();
-    server.addProtoService(mathService, {
+    server.addService(mathServiceAttrs, {
       'div': function(stream) {},
       'divMany': function(stream) {},
       'fib': function(stream) {},
       'sum': function(stream) {}
     });
     var port = server.bind('localhost:0', server_insecure_creds);
-    var Client = surface_client.makeProtobufClientConstructor(mathService);
+    var Client = surface_client.makeClientConstructor(mathServiceAttrs);
     client = new Client('localhost:' + port, grpc.credentials.createInsecure());
     server.start();
   });
diff --git a/src/node/test/test_messages.proto b/src/node/test/test_messages.proto
index a1a6a32..ae70f6e 100644
--- a/src/node/test/test_messages.proto
+++ b/src/node/test/test_messages.proto
@@ -41,3 +41,20 @@
   bytes bytes_field = 1;
   repeated int32 repeated_field = 2;
 }
+
+message OneOfValues {
+  oneof oneof_choice {
+    int32 int_choice = 1;
+    string string_choice = 2;
+  }
+}
+
+enum TestEnum {
+  ZERO = 0;
+  ONE = 1;
+  TWO = 2;
+}
+
+message EnumValues {
+  TestEnum enum_value = 1;
+}
\ No newline at end of file
diff --git a/templates/package.json.template b/templates/package.json.template
index d093883..b69fd28 100644
--- a/templates/package.json.template
+++ b/templates/package.json.template
@@ -36,7 +36,7 @@
       "lodash": "^4.15.0",
       "nan": "^2.0.0",
       "node-pre-gyp": "^0.6.0",
-      "protobufjs": "^5.0.0",
+      "protobufjs": "^6.7.0",
       "cares": "^1.1.5"
     },
     "devDependencies": {