syncbase cgo: adds watch/sync impls, plus a few cleanups

- adds watch API and implementation
- implements sync-related functions
- adds top-level comment about the meaning of 'cName' in
  the various cgo methods
- cleans up bridge/blessings.go a bit (moves a comment that
  belongs in bridge/mojo/blessings.go)
- adds TODO about ListDatabases being broken

Change-Id: I3bc7fb39c6339a079c72faaf3b1600d7a460a3f1
diff --git a/services/syncbase/bridge/blessings.go b/services/syncbase/bridge/blessings.go
index a1bbaac..e8dc8ba 100644
--- a/services/syncbase/bridge/blessings.go
+++ b/services/syncbase/bridge/blessings.go
@@ -2,11 +2,12 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// This file provides utilities for exchanging an OAuth token for a blessing
-// via the Vanadium identity provider, and initializing a Vanadium context
-// with this blessing.
+// This file provides utilities for exchanging an OAuth token for a blessing via
+// the Vanadium identity provider, and initializing a Vanadium context with this
+// blessing.
 //
-// See v.io/x/ref/services/syncbase/bridge/mojo/blessings.go for more documentation.
+// See v.io/x/ref/services/syncbase/bridge/mojo/blessings.go for more
+// documentation.
 
 package bridge
 
@@ -25,20 +26,12 @@
 	seclib "v.io/x/ref/lib/security"
 )
 
-// SetBlessings exchanges an oauth token for blessings from the dev.v.io server.
-// Currently oauthProvider must be set to "google".
+// SetBlessings exchanges an OAuth token for blessings from the dev.v.io server.
+// Currently, oauthProvider must be set to "google".
 func SetBlessings(ctx *context.T, oauthProvider string, oauthToken string) error {
 	if strings.ToLower(oauthProvider) != "google" {
-		return fmt.Errorf("unsupported oauthProvider %q; currently, \"google\" is the only supported provider",
-			oauthProvider)
+		return fmt.Errorf("unsupported oauthProvider %q; currently, \"google\" is the only supported provider", oauthProvider)
 	}
-	// Get an OAuth2 token from the mojo authentication service.
-	// TODO(ashankar,ataly): This is almost a duplicate of what
-	// is in
-	// https://github.com/domokit/mojo/blob/master/services/vanadium/security/principal_service.go
-	// - at some point this needs to be cleared up and this
-	// syncbase should use the principal service?
-	ctx.Infof("Obtained OAuth2 token, will exchange for blessings")
 	p := v23.GetPrincipal(ctx)
 	blessings, err := token2blessings(oauthToken, p.PublicKey())
 	if err != nil {
diff --git a/services/syncbase/bridge/cgo/impl.go b/services/syncbase/bridge/cgo/impl.go
index c218b67..b58fd86 100644
--- a/services/syncbase/bridge/cgo/impl.go
+++ b/services/syncbase/bridge/cgo/impl.go
@@ -4,12 +4,18 @@
 
 // +build cgo
 
+// TODO(sadovsky): Make DbWatchPatterns and CollectionScan cancelable, e.g. by
+// returning a cancel closure handle to the client.
+
 // Syncbase C/Cgo API. Our strategy is to translate Cgo requests into Vanadium
 // stub requests, and Vanadium stub responses into Cgo responses. As part of
 // this procedure, we synthesize "fake" ctx and call objects to pass to the
 // Vanadium stubs.
 //
 // Implementation notes:
+// - This API partly mirrors the Syncbase RPC API. Many methods take 'cName' as
+//   their first argument; this is a service-relative Vanadium object name. For
+//   example, the 'cName' argument to DbCreate is an encoded database id.
 // - All exported function and type names start with "v23_syncbase_", to avoid
 //   colliding with desired client library names.
 // - Exported functions take input arguments by value, optional input arguments
@@ -32,6 +38,8 @@
 	"v.io/v23/rpc"
 	"v.io/v23/services/permissions"
 	wire "v.io/v23/services/syncbase"
+	"v.io/v23/services/watch"
+	"v.io/v23/syncbase"
 	"v.io/v23/syncbase/util"
 	"v.io/v23/verror"
 	"v.io/v23/vom"
@@ -43,6 +51,13 @@
 /*
 #include "lib.h"
 
+static void CallDbWatchPatternsCallbacksOnChange(v23_syncbase_DbWatchPatternsCallbacks cbs, v23_syncbase_WatchChange wc) {
+  cbs.onChange(cbs.hOnChange, wc);
+}
+static void CallDbWatchPatternsCallbacksOnError(v23_syncbase_DbWatchPatternsCallbacks cbs, v23_syncbase_VError err) {
+  cbs.onError(cbs.hOnChange, cbs.hOnError, err);
+}
+
 static void CallCollectionScanCallbacksOnKeyValue(v23_syncbase_CollectionScanCallbacks cbs, v23_syncbase_KeyValue kv) {
   cbs.onKeyValue(cbs.hOnKeyValue, kv);
 }
@@ -165,6 +180,7 @@
 
 //export v23_syncbase_ServiceListDatabases
 func v23_syncbase_ServiceListDatabases(cIds *C.v23_syncbase_Ids, cErr *C.v23_syncbase_VError) {
+	// TODO(sadovsky): This is broken; it always returns an empty list.
 	listChildIds("", cIds, cErr)
 }
 
@@ -307,8 +323,6 @@
 	cErr.init(stub.SetPermissions(ctx, call, perms, version))
 }
 
-// TODO(sadovsky): Add watch API.
-
 //export v23_syncbase_DbGetResumeMarker
 func v23_syncbase_DbGetResumeMarker(cName, cBatchHandle C.v23_syncbase_String, cMarker *C.v23_syncbase_Bytes, cErr *C.v23_syncbase_VError) {
 	name := cName.toString()
@@ -327,11 +341,64 @@
 	cMarker.init(marker)
 }
 
+type watchStreamImpl struct {
+	ctx *context.T
+	cbs C.v23_syncbase_DbWatchPatternsCallbacks
+}
+
+func (s *watchStreamImpl) Send(item interface{}) error {
+	wireWC, ok := item.(watch.Change)
+	if !ok {
+		return verror.NewErrInternal(s.ctx)
+	}
+	// C.CallDbWatchPatternsCallbacksOnChange() blocks until the client acks the
+	// previous invocation, thus providing flow control.
+	cWatchChange := C.v23_syncbase_WatchChange{}
+	cWatchChange.init(syncbase.ToWatchChange(wireWC))
+	C.CallDbWatchPatternsCallbacksOnChange(s.cbs, cWatchChange)
+	return nil
+}
+
+func (s *watchStreamImpl) Recv(_ interface{}) error {
+	// This should never be called.
+	return verror.NewErrInternal(s.ctx)
+}
+
+var _ rpc.Stream = (*watchStreamImpl)(nil)
+
+//export v23_syncbase_DbWatchPatterns
+func v23_syncbase_DbWatchPatterns(cName, cResumeMarker C.v23_syncbase_String, cPatterns C.v23_syncbase_CollectionRowPatterns, cbs C.v23_syncbase_DbWatchPatternsCallbacks, cErr *C.v23_syncbase_VError) {
+	name := cName.toString()
+	resumeMarker := watch.ResumeMarker(cResumeMarker.toString())
+	patterns := cPatterns.toCollectionRowPatterns()
+	ctx, call := b.NewCtxCall(name, bridge.MethodDesc(wire.DatabaseWatcherDesc, "WatchPatterns"))
+	stub, err := b.GetDb(ctx, call, name)
+	if err != nil {
+		cErr.init(err)
+		return
+	}
+
+	streamStub := &wire.DatabaseWatcherWatchPatternsServerCallStub{struct {
+		rpc.Stream
+		rpc.ServerCall
+	}{
+		&watchStreamImpl{ctx: ctx, cbs: cbs},
+		call,
+	}}
+
+	go func() {
+		err := stub.WatchPatterns(ctx, streamStub, resumeMarker, patterns)
+		// Note: Since we are now streaming, any new error must be sent back on the
+		// stream; the function itself should not return an error at this point.
+		cErr := C.v23_syncbase_VError{}
+		cErr.init(err)
+		C.CallDbWatchPatternsCallbacksOnError(cbs, cErr)
+	}()
+}
+
 ////////////////////////////////////////
 // SyncgroupManager
 
-// FIXME(sadovsky): Implement "NewErrNotImplemented" methods below.
-
 //export v23_syncbase_DbListSyncgroups
 func v23_syncbase_DbListSyncgroups(cName C.v23_syncbase_String, cIds *C.v23_syncbase_Ids, cErr *C.v23_syncbase_VError) {
 	name := cName.toString()
@@ -341,8 +408,12 @@
 		cErr.init(err)
 		return
 	}
-	cErr.init(verror.NewErrNotImplemented(nil))
-	_ = stub // prevent "declared and not used"
+	ids, err := stub.ListSyncgroups(ctx, call)
+	if err != nil {
+		cErr.init(err)
+		return
+	}
+	cIds.init(ids)
 }
 
 //export v23_syncbase_DbCreateSyncgroup
@@ -357,13 +428,14 @@
 		cErr.init(err)
 		return
 	}
-	cErr.init(verror.NewErrNotImplemented(nil))
-	_, _, _, _ = sgId, spec, myInfo, stub // prevent "declared and not used"
+	cErr.init(stub.CreateSyncgroup(ctx, call, sgId, spec, myInfo))
 }
 
 //export v23_syncbase_DbJoinSyncgroup
-func v23_syncbase_DbJoinSyncgroup(cName C.v23_syncbase_String, cSgId C.v23_syncbase_Id, cMyInfo C.v23_syncbase_SyncgroupMemberInfo, cSpec *C.v23_syncbase_SyncgroupSpec, cErr *C.v23_syncbase_VError) {
+func v23_syncbase_DbJoinSyncgroup(cName, cRemoteSyncbaseName C.v23_syncbase_String, cExpectedSyncbaseBlessings C.v23_syncbase_Strings, cSgId C.v23_syncbase_Id, cMyInfo C.v23_syncbase_SyncgroupMemberInfo, cSpec *C.v23_syncbase_SyncgroupSpec, cErr *C.v23_syncbase_VError) {
 	name := cName.toString()
+	remoteSyncbaseName := cRemoteSyncbaseName.toString()
+	expectedSyncbaseBlessings := cExpectedSyncbaseBlessings.toStrings()
 	sgId := cSgId.toId()
 	myInfo := cMyInfo.toSyncgroupMemberInfo()
 	ctx, call := b.NewCtxCall(name, bridge.MethodDesc(wire.SyncgroupManagerDesc, "JoinSyncgroup"))
@@ -372,8 +444,12 @@
 		cErr.init(err)
 		return
 	}
-	cErr.init(verror.NewErrNotImplemented(nil))
-	_, _, _ = sgId, myInfo, stub // prevent "declared and not used"
+	spec, err := stub.JoinSyncgroup(ctx, call, remoteSyncbaseName, expectedSyncbaseBlessings, sgId, myInfo)
+	if err != nil {
+		cErr.init(err)
+		return
+	}
+	cSpec.init(spec)
 }
 
 //export v23_syncbase_DbLeaveSyncgroup
@@ -386,8 +462,7 @@
 		cErr.init(err)
 		return
 	}
-	cErr.init(verror.NewErrNotImplemented(nil))
-	_, _ = sgId, stub // prevent "declared and not used"
+	cErr.init(stub.LeaveSyncgroup(ctx, call, sgId))
 }
 
 //export v23_syncbase_DbDestroySyncgroup
@@ -400,8 +475,7 @@
 		cErr.init(err)
 		return
 	}
-	cErr.init(verror.NewErrNotImplemented(nil))
-	_, _ = sgId, stub // prevent "declared and not used"
+	cErr.init(stub.DestroySyncgroup(ctx, call, sgId))
 }
 
 //export v23_syncbase_DbEjectFromSyncgroup
@@ -415,8 +489,7 @@
 		cErr.init(err)
 		return
 	}
-	cErr.init(verror.NewErrNotImplemented(nil))
-	_, _, _ = sgId, member, stub // prevent "declared and not used"
+	cErr.init(stub.EjectFromSyncgroup(ctx, call, sgId, member))
 }
 
 //export v23_syncbase_DbGetSyncgroupSpec
@@ -429,8 +502,13 @@
 		cErr.init(err)
 		return
 	}
-	cErr.init(verror.NewErrNotImplemented(nil))
-	_, _ = sgId, stub // prevent "declared and not used"
+	spec, version, err := stub.GetSyncgroupSpec(ctx, call, sgId)
+	if err != nil {
+		cErr.init(err)
+		return
+	}
+	cSpec.init(spec)
+	cVersion.init(version)
 }
 
 //export v23_syncbase_DbSetSyncgroupSpec
@@ -445,8 +523,7 @@
 		cErr.init(err)
 		return
 	}
-	cErr.init(verror.NewErrNotImplemented(nil))
-	_, _, _, _ = sgId, spec, version, stub // prevent "declared and not used"
+	cErr.init(stub.SetSyncgroupSpec(ctx, call, sgId, spec, version))
 }
 
 //export v23_syncbase_DbGetSyncgroupMembers
@@ -459,8 +536,12 @@
 		cErr.init(err)
 		return
 	}
-	cErr.init(verror.NewErrNotImplemented(nil))
-	_, _ = sgId, stub // prevent "declared and not used"
+	members, err := stub.GetSyncgroupMembers(ctx, call, sgId)
+	if err != nil {
+		cErr.init(err)
+		return
+	}
+	cMembers.init(members)
 }
 
 ////////////////////////////////////////
@@ -586,8 +667,6 @@
 
 var _ rpc.Stream = (*scanStreamImpl)(nil)
 
-// TODO(nlacasse): Provide some way for the client to cancel the stream.
-
 //export v23_syncbase_CollectionScan
 func v23_syncbase_CollectionScan(cName, cBatchHandle C.v23_syncbase_String, cStart, cLimit C.v23_syncbase_Bytes, cbs C.v23_syncbase_CollectionScanCallbacks, cErr *C.v23_syncbase_VError) {
 	name := cName.toString()
@@ -600,7 +679,7 @@
 		return
 	}
 
-	collectionScanServerCallStub := &wire.CollectionScanServerCallStub{struct {
+	streamStub := &wire.CollectionScanServerCallStub{struct {
 		rpc.Stream
 		rpc.ServerCall
 	}{
@@ -609,10 +688,9 @@
 	}}
 
 	go func() {
-		err := stub.Scan(ctx, collectionScanServerCallStub, batchHandle, start, limit)
-		// NOTE(nlacasse): Since we are already streaming, we send any error back to
-		// the client on the stream. The CollectionScan function itself should not
-		// return an error at this point.
+		err := stub.Scan(ctx, streamStub, batchHandle, start, limit)
+		// Note: Since we are now streaming, any new error must be sent back on the
+		// stream; the function itself should not return an error at this point.
 		cErr := C.v23_syncbase_VError{}
 		cErr.init(err)
 		C.CallCollectionScanCallbacksOnDone(cbs, cErr)
diff --git a/services/syncbase/bridge/cgo/lib.h b/services/syncbase/bridge/cgo/lib.h
index 11d416a..fc8565a 100644
--- a/services/syncbase/bridge/cgo/lib.h
+++ b/services/syncbase/bridge/cgo/lib.h
@@ -8,8 +8,6 @@
 #include <stdbool.h>
 #include <stdint.h>
 
-// TODO(sadovsky): Add types and functions for watch and sync.
-
 ////////////////////////////////////////
 // Generic types
 
@@ -71,6 +69,35 @@
   bool readOnly;
 } v23_syncbase_BatchOptions;
 
+// syncbase.CollectionRowPattern
+typedef struct {
+  v23_syncbase_String collectionBlessing;
+  v23_syncbase_String collectionName;
+  v23_syncbase_String rowKey;
+} v23_syncbase_CollectionRowPattern;
+
+// []syncbase.CollectionRowPattern
+typedef struct {
+  v23_syncbase_CollectionRowPattern* p;
+  int n;
+} v23_syncbase_CollectionRowPatterns;
+
+typedef enum v23_syncbase_ChangeType {
+  kPut = 0,
+  kDelete = 1
+} v23_syncbase_ChangeType;
+
+// syncbase.WatchChange
+typedef struct {
+  v23_syncbase_Id collection;
+  v23_syncbase_String row;
+  v23_syncbase_ChangeType changeType;
+  v23_syncbase_Bytes value;
+  v23_syncbase_String resumeMarker;
+  bool fromSync;
+  bool continued;
+} v23_syncbase_WatchChange;
+
 // syncbase.KeyValue
 typedef struct {
   v23_syncbase_String key;
@@ -80,6 +107,7 @@
 // syncbase.SyncgroupSpec
 typedef struct {
   v23_syncbase_String description;
+  v23_syncbase_String publishSyncbaseName;
   v23_syncbase_Permissions perms;
   v23_syncbase_Ids collections;
   v23_syncbase_Strings mountTables;
@@ -110,6 +138,13 @@
 typedef int v23_syncbase_Handle;
 
 typedef struct {
+  v23_syncbase_Handle hOnChange;
+  v23_syncbase_Handle hOnError;
+  void (*onChange)(v23_syncbase_Handle hOnChange, v23_syncbase_WatchChange);
+  void (*onError)(v23_syncbase_Handle hOnChange, v23_syncbase_Handle hOnError, v23_syncbase_VError);
+} v23_syncbase_DbWatchPatternsCallbacks;
+
+typedef struct {
   v23_syncbase_Handle hOnKeyValue;
   v23_syncbase_Handle hOnDone;
   void (*onKeyValue)(v23_syncbase_Handle hOnKeyValue, v23_syncbase_KeyValue);
diff --git a/services/syncbase/bridge/cgo/types.go b/services/syncbase/bridge/cgo/types.go
index dff0408..3dbfc9c 100644
--- a/services/syncbase/bridge/cgo/types.go
+++ b/services/syncbase/bridge/cgo/types.go
@@ -10,7 +10,9 @@
 
 	"v.io/v23/security/access"
 	wire "v.io/v23/services/syncbase"
+	"v.io/v23/syncbase"
 	"v.io/v23/verror"
+	"v.io/v23/vom"
 )
 
 // All "x.toFoo" methods free the memory associated with x.
@@ -22,7 +24,7 @@
 */
 import "C"
 
-////////////////////////////////////////
+////////////////////////////////////////////////////////////
 // C.v23_syncbase_Bool
 
 func (x *C.v23_syncbase_Bool) init(b bool) {
@@ -40,7 +42,7 @@
 	return true
 }
 
-////////////////////////////////////////
+////////////////////////////////////////////////////////////
 // C.v23_syncbase_String
 
 func (x *C.v23_syncbase_String) init(s string) {
@@ -56,7 +58,7 @@
 	return C.GoStringN(x.p, x.n)
 }
 
-////////////////////////////////////////
+////////////////////////////////////////////////////////////
 // C.v23_syncbase_Bytes
 
 func init() {
@@ -67,7 +69,7 @@
 
 func (x *C.v23_syncbase_Bytes) init(b []byte) {
 	x.n = C.int(len(b))
-	x.p = (*C.uint8_t)(C.malloc(C.size_t(len(b))))
+	x.p = (*C.uint8_t)(C.malloc(C.size_t(x.n)))
 	C.memcpy(unsafe.Pointer(x.p), unsafe.Pointer(&b[0]), C.size_t(len(b)))
 }
 
@@ -79,7 +81,7 @@
 	return C.GoBytes(unsafe.Pointer(x.p), x.n)
 }
 
-////////////////////////////////////////
+////////////////////////////////////////////////////////////
 // C.v23_syncbase_Strings
 
 func (x *C.v23_syncbase_Strings) at(i int) *C.v23_syncbase_String {
@@ -88,7 +90,7 @@
 
 func (x *C.v23_syncbase_Strings) init(strs []string) {
 	x.n = C.int(len(strs))
-	x.p = (*C.v23_syncbase_String)(C.malloc(C.size_t(len(strs)) * C.sizeof_v23_syncbase_String))
+	x.p = (*C.v23_syncbase_String)(C.malloc(C.size_t(x.n) * C.sizeof_v23_syncbase_String))
 	for i, v := range strs {
 		x.at(i).init(v)
 	}
@@ -106,7 +108,7 @@
 	return res
 }
 
-////////////////////////////////////////
+////////////////////////////////////////////////////////////
 // C.v23_syncbase_VError
 
 func (x *C.v23_syncbase_VError) init(err error) {
@@ -119,7 +121,7 @@
 	x.stack.init(verror.Stack(err).String())
 }
 
-////////////////////////////////////////
+////////////////////////////////////////////////////////////
 // C.v23_syncbase_Permissions
 
 func (x *C.v23_syncbase_Permissions) init(perms access.Permissions) {
@@ -142,7 +144,7 @@
 	return perms
 }
 
-////////////////////////////////////////
+////////////////////////////////////////////////////////////
 // C.v23_syncbase_Id
 
 func (x *C.v23_syncbase_Id) init(id wire.Id) {
@@ -157,7 +159,7 @@
 	}
 }
 
-////////////////////////////////////////
+////////////////////////////////////////////////////////////
 // C.v23_syncbase_Ids
 
 func (x *C.v23_syncbase_Ids) at(i int) *C.v23_syncbase_Id {
@@ -166,13 +168,25 @@
 
 func (x *C.v23_syncbase_Ids) init(ids []wire.Id) {
 	x.n = C.int(len(ids))
-	x.p = (*C.v23_syncbase_Id)(C.malloc(C.size_t(len(ids)) * C.sizeof_v23_syncbase_Id))
+	x.p = (*C.v23_syncbase_Id)(C.malloc(C.size_t(x.n) * C.sizeof_v23_syncbase_Id))
 	for i, v := range ids {
 		x.at(i).init(v)
 	}
 }
 
-////////////////////////////////////////
+func (x *C.v23_syncbase_Ids) toIds() []wire.Id {
+	if x.p == nil {
+		return nil
+	}
+	defer C.free(unsafe.Pointer(x.p))
+	res := make([]wire.Id, x.n)
+	for i := 0; i < int(x.n); i++ {
+		res[i] = x.at(i).toId()
+	}
+	return res
+}
+
+////////////////////////////////////////////////////////////
 // C.v23_syncbase_BatchOptions
 
 func (x *C.v23_syncbase_BatchOptions) init(opts wire.BatchOptions) {
@@ -187,7 +201,69 @@
 	}
 }
 
-////////////////////////////////////////
+////////////////////////////////////////////////////////////
+// C.v23_syncbase_CollectionRowPattern
+
+func (x *C.v23_syncbase_CollectionRowPattern) init(crp wire.CollectionRowPattern) {
+	x.collectionBlessing.init(crp.CollectionBlessing)
+	x.collectionName.init(crp.CollectionName)
+	x.rowKey.init(crp.RowKey)
+}
+
+func (x *C.v23_syncbase_CollectionRowPattern) toCollectionRowPattern() wire.CollectionRowPattern {
+	return wire.CollectionRowPattern{
+		CollectionBlessing: x.collectionBlessing.toString(),
+		CollectionName:     x.collectionName.toString(),
+		RowKey:             x.rowKey.toString(),
+	}
+}
+
+////////////////////////////////////////////////////////////
+// C.v23_syncbase_CollectionRowPatterns
+
+func (x *C.v23_syncbase_CollectionRowPatterns) at(i int) *C.v23_syncbase_CollectionRowPattern {
+	return (*C.v23_syncbase_CollectionRowPattern)(unsafe.Pointer(uintptr(unsafe.Pointer(x.p)) + uintptr(C.size_t(i)*C.sizeof_v23_syncbase_CollectionRowPattern)))
+}
+
+func (x *C.v23_syncbase_CollectionRowPatterns) init(crps []wire.CollectionRowPattern) {
+	x.n = C.int(len(crps))
+	x.p = (*C.v23_syncbase_CollectionRowPattern)(C.malloc(C.size_t(x.n) * C.sizeof_v23_syncbase_CollectionRowPattern))
+	for i, v := range crps {
+		x.at(i).init(v)
+	}
+}
+
+func (x *C.v23_syncbase_CollectionRowPatterns) toCollectionRowPatterns() []wire.CollectionRowPattern {
+	if x.p == nil {
+		return nil
+	}
+	defer C.free(unsafe.Pointer(x.p))
+	res := make([]wire.CollectionRowPattern, x.n)
+	for i := 0; i < int(x.n); i++ {
+		res[i] = x.at(i).toCollectionRowPattern()
+	}
+	return res
+}
+
+////////////////////////////////////////////////////////////
+// C.v23_syncbase_WatchChange
+
+func (x *C.v23_syncbase_WatchChange) init(wc syncbase.WatchChange) error {
+	x.collection.init(wc.Collection)
+	x.row.init(wc.Row)
+	x.changeType = C.v23_syncbase_ChangeType(wc.ChangeType)
+	value, err := vom.Encode(wc.Value)
+	if err != nil {
+		return err
+	}
+	x.value.init(value)
+	x.resumeMarker.init(string(wc.ResumeMarker))
+	x.fromSync = C.bool(wc.FromSync)
+	x.continued = C.bool(wc.Continued)
+	return nil
+}
+
+////////////////////////////////////////////////////////////
 // C.v23_syncbase_KeyValue
 
 func (x *C.v23_syncbase_KeyValue) init(key string, value []byte) {
@@ -195,33 +271,62 @@
 	x.value.init(value)
 }
 
-// FIXME(sadovsky): Implement stubbed-out methods below.
-
-////////////////////////////////////////
+////////////////////////////////////////////////////////////
 // C.v23_syncbase_SyncgroupSpec
 
 func (x *C.v23_syncbase_SyncgroupSpec) init(spec wire.SyncgroupSpec) {
-
+	x.description.init(spec.Description)
+	x.publishSyncbaseName.init(spec.PublishSyncbaseName)
+	x.perms.init(spec.Perms)
+	x.collections.init(spec.Collections)
+	x.mountTables.init(spec.MountTables)
+	x.isPrivate = C.bool(spec.IsPrivate)
 }
 
 func (x *C.v23_syncbase_SyncgroupSpec) toSyncgroupSpec() wire.SyncgroupSpec {
-	return wire.SyncgroupSpec{}
+	return wire.SyncgroupSpec{
+		Description:         x.description.toString(),
+		PublishSyncbaseName: x.publishSyncbaseName.toString(),
+		Perms:               x.perms.toPermissions(),
+		Collections:         x.collections.toIds(),
+		MountTables:         x.mountTables.toStrings(),
+		IsPrivate:           bool(x.isPrivate),
+	}
 }
 
-////////////////////////////////////////
+////////////////////////////////////////////////////////////
 // C.v23_syncbase_SyncgroupMemberInfo
 
 func (x *C.v23_syncbase_SyncgroupMemberInfo) init(member wire.SyncgroupMemberInfo) {
-
+	x.syncPriority = C.uint8_t(member.SyncPriority)
+	x.blobDevType = C.uint8_t(member.BlobDevType)
 }
 
 func (x *C.v23_syncbase_SyncgroupMemberInfo) toSyncgroupMemberInfo() wire.SyncgroupMemberInfo {
-	return wire.SyncgroupMemberInfo{}
+	return wire.SyncgroupMemberInfo{
+		SyncPriority: byte(x.syncPriority),
+		BlobDevType:  byte(x.blobDevType),
+	}
 }
 
-////////////////////////////////////////
+////////////////////////////////////////////////////////////
 // C.v23_syncbase_SyncgroupMemberInfoMap
 
-func (x *C.v23_syncbase_SyncgroupMemberInfoMap) init(members map[string]wire.SyncgroupMemberInfo) {
+func (x *C.v23_syncbase_SyncgroupMemberInfoMap) at(i int) (*C.v23_syncbase_String, *C.v23_syncbase_SyncgroupMemberInfo) {
+	k := (*C.v23_syncbase_String)(unsafe.Pointer(uintptr(unsafe.Pointer(x.keys)) + uintptr(C.size_t(i)*C.sizeof_v23_syncbase_String)))
+	v := (*C.v23_syncbase_SyncgroupMemberInfo)(unsafe.Pointer(uintptr(unsafe.Pointer(x.values)) + uintptr(C.size_t(i)*C.sizeof_v23_syncbase_SyncgroupMemberInfo)))
+	return k, v
+}
 
+func (x *C.v23_syncbase_SyncgroupMemberInfoMap) init(members map[string]wire.SyncgroupMemberInfo) {
+	x.n = C.int(len(members))
+	x.keys = (*C.v23_syncbase_String)(C.malloc(C.size_t(x.n) * C.sizeof_v23_syncbase_String))
+	x.values = (*C.v23_syncbase_SyncgroupMemberInfo)(C.malloc(C.size_t(x.n) * C.sizeof_v23_syncbase_SyncgroupMemberInfo))
+	i := 0
+	for k, v := range members {
+		ck, cv := x.at(i)
+		ck.init(k)
+		cv.init(v)
+		i++
+	}
 }
diff --git a/services/syncbase/bridge/mojo/blessings.go b/services/syncbase/bridge/mojo/blessings.go
index 1b2441e..fbce4c1 100644
--- a/services/syncbase/bridge/mojo/blessings.go
+++ b/services/syncbase/bridge/mojo/blessings.go
@@ -78,11 +78,11 @@
 		v23ctx.Infof("Using default blessings for non-Android OS (%v)", runtime.GOOS)
 		return nil
 	}
-	// TODO(ashankar,ataly): This is almost a duplicate of what
-	// is in
+	// Get an OAuth2 token from the mojo authentication service.
+	// TODO(ashankar,ataly): This is almost a duplicate of:
 	// https://github.com/domokit/mojo/blob/master/services/vanadium/security/principal_service.go
-	// - at some point this needs to be cleared up and this
-	// syncbase should use the principal service?
+	// At some point this needs to be cleared up - the syncbase mojo service
+	// should talk to the principal mojo service.
 	token, err := oauthToken(appctx)
 	if err != nil {
 		if _, ok := err.(selectAccountFailed); ok {
@@ -93,6 +93,7 @@
 		}
 		return err
 	}
+	ctx.Infof("Obtained OAuth2 token, will exchange for blessings")
 	return bridge.SetBlessings(v23ctx, "google", token)
 }
 
diff --git a/services/syncbase/bridge/mojo/impl.go b/services/syncbase/bridge/mojo/impl.go
index 8139e23..e5281aa 100644
--- a/services/syncbase/bridge/mojo/impl.go
+++ b/services/syncbase/bridge/mojo/impl.go
@@ -4,8 +4,10 @@
 
 // +build mojo
 
-// TODO(sadovsky): Finish updating to reflect new, simplified API. Some, but not
-// all, of this code has been updated.
+// NOTE(sadovsky): The code below reflects some, but not all, of the Syncbase
+// API changes and simplifications. If at some point we choose to update this
+// code, the person doing this work should compare against bridge/cgo/impl.go to
+// figure out what needs to change.
 
 // Implementation of Syncbase Mojo stubs. Our strategy is to translate Mojo stub
 // requests into Vanadium stub requests, and Vanadium stub responses into Mojo
@@ -13,6 +15,9 @@
 // objects to pass to the Vanadium stubs.
 //
 // Implementation notes:
+// - This API partly mirrors the Syncbase RPC API. Many methods take 'name' as
+//   their first argument; this is a service-relative Vanadium object name. For
+//   example, the 'name' argument to DbCreate is an encoded database id.
 // - Variables with Mojo-specific types have names that start with "m".
 
 package bridge_mojo