maintnerd: start of gRPC service for maintner

Also, update docs on how to re-generate.
Switches to using go4.org/grpc and go4.org/grpc-codegen/protoc-gen-go4grpc
for now.

Updates golang/go#19866
Updates golang/go#20222

Change-Id: Ifa8a123fca2a30f17270c3c558b7395a02064eae
Reviewed-on: https://go-review.googlesource.com/43560
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/maintner/git.go b/maintner/git.go
index 80d0d0e..980ca4e 100644
--- a/maintner/git.go
+++ b/maintner/git.go
@@ -463,6 +463,21 @@
 
 }
 
+// GitCommit returns the provided git commit, or nil if it's unknown.
+func (c *Corpus) GitCommit(hash string) *GitCommit {
+	if len(hash) != 40 {
+		// TODO: support prefix lookups. build a trie. But
+		// for now just avoid panicking in gitHashFromHexStr.
+		return nil
+	}
+	var buf [20]byte
+	_, err := decodeHexStr(buf[:], hash)
+	if err != nil {
+		return nil
+	}
+	return c.gitCommit[GitHash(buf[:])]
+}
+
 // v is like '[+-]hhmm'
 // c.mu must be held for writing.
 func (c *Corpus) gitLocation(v []byte) *time.Location {
@@ -483,3 +498,37 @@
 	c.zoneCache[s] = loc
 	return loc
 }
+
+func decodeHexStr(dst []byte, src string) (int, error) {
+	if len(src)%2 == 1 {
+		return 0, hex.ErrLength
+	}
+
+	for i := 0; i < len(src)/2; i++ {
+		a, ok := fromHexChar(src[i*2])
+		if !ok {
+			return 0, hex.InvalidByteError(src[i*2])
+		}
+		b, ok := fromHexChar(src[i*2+1])
+		if !ok {
+			return 0, hex.InvalidByteError(src[i*2+1])
+		}
+		dst[i] = (a << 4) | b
+	}
+
+	return len(src) / 2, nil
+}
+
+// fromHexChar converts a hex character into its value and a success flag.
+func fromHexChar(c byte) (byte, bool) {
+	switch {
+	case '0' <= c && c <= '9':
+		return c - '0', true
+	case 'a' <= c && c <= 'f':
+		return c - 'a' + 10, true
+	case 'A' <= c && c <= 'F':
+		return c - 'A' + 10, true
+	}
+
+	return 0, false
+}
diff --git a/maintner/maintner.go b/maintner/maintner.go
index 69adaea..a3c473e 100644
--- a/maintner/maintner.go
+++ b/maintner/maintner.go
@@ -123,16 +123,6 @@
 	return nil
 }
 
-// GitCommit returns the provided git commit, or nil if it's unknown.
-func (c *Corpus) GitCommit(hash string) *GitCommit {
-	if len(hash) != 40 {
-		// TODO: support prefix lookups. build a trie. But
-		// for now just avoid panicking in gitHashFromHexStr.
-		return nil
-	}
-	return c.gitCommit[c.gitHashFromHexStr(hash)]
-}
-
 // mustProtoFromTime turns a time.Time into a *timestamp.Timestamp or panics if
 // in is invalid.
 func mustProtoFromTime(in time.Time) *timestamp.Timestamp {
diff --git a/maintner/maintnerd/api.go b/maintner/maintnerd/api.go
new file mode 100644
index 0000000..3970600
--- /dev/null
+++ b/maintner/maintnerd/api.go
@@ -0,0 +1,42 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"context"
+	"errors"
+
+	"golang.org/x/build/maintner"
+	"golang.org/x/build/maintner/maintnerd/apipb"
+)
+
+// apiService implements apipb.MaintnerServiceServer using the Corpus c.
+type apiService struct {
+	c *maintner.Corpus
+}
+
+func (s apiService) HasAncestor(ctx context.Context, req *apipb.HasAncestorRequest) (*apipb.HasAncestorResponse, error) {
+	if len(req.Commit) != 40 {
+		return nil, errors.New("invalid Commit")
+	}
+	if len(req.Ancestor) != 40 {
+		return nil, errors.New("invalid Ancestor")
+	}
+	s.c.RLock()
+	defer s.c.RUnlock()
+
+	commit := s.c.GitCommit(req.Commit)
+	res := new(apipb.HasAncestorResponse)
+	if commit == nil {
+		// TODO: wait for it? kick off a fetch of it and then answer?
+		// optional?
+		res.UnknownCommit = true
+		return res, nil
+	}
+	if a := s.c.GitCommit(req.Ancestor); a != nil {
+		res.HasAncestor = commit.HasAncestor(a)
+	}
+	return res, nil
+}
diff --git a/maintner/maintnerd/apipb/api.pb.go b/maintner/maintnerd/apipb/api.pb.go
new file mode 100644
index 0000000..cb77811
--- /dev/null
+++ b/maintner/maintnerd/apipb/api.pb.go
@@ -0,0 +1,184 @@
+// Code generated by protoc-gen-go4grpc; DO NOT EDIT
+// source: api.proto
+
+/*
+Package apipb is a generated protocol buffer package.
+
+It is generated from these files:
+	api.proto
+
+It has these top-level messages:
+	HasAncestorRequest
+	HasAncestorResponse
+*/
+package apipb
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+import (
+	context "context"
+	grpc "go4.org/grpc"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type HasAncestorRequest struct {
+	Commit   string `protobuf:"bytes,1,opt,name=commit" json:"commit,omitempty"`
+	Ancestor string `protobuf:"bytes,2,opt,name=ancestor" json:"ancestor,omitempty"`
+}
+
+func (m *HasAncestorRequest) Reset()                    { *m = HasAncestorRequest{} }
+func (m *HasAncestorRequest) String() string            { return proto.CompactTextString(m) }
+func (*HasAncestorRequest) ProtoMessage()               {}
+func (*HasAncestorRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+func (m *HasAncestorRequest) GetCommit() string {
+	if m != nil {
+		return m.Commit
+	}
+	return ""
+}
+
+func (m *HasAncestorRequest) GetAncestor() string {
+	if m != nil {
+		return m.Ancestor
+	}
+	return ""
+}
+
+type HasAncestorResponse struct {
+	// has_ancestor is whether ancestor appears in commit's history.
+	HasAncestor bool `protobuf:"varint,1,opt,name=has_ancestor,json=hasAncestor" json:"has_ancestor,omitempty"`
+	// unknown_commit is true if the provided commit was unknown.
+	UnknownCommit bool `protobuf:"varint,2,opt,name=unknown_commit,json=unknownCommit" json:"unknown_commit,omitempty"`
+}
+
+func (m *HasAncestorResponse) Reset()                    { *m = HasAncestorResponse{} }
+func (m *HasAncestorResponse) String() string            { return proto.CompactTextString(m) }
+func (*HasAncestorResponse) ProtoMessage()               {}
+func (*HasAncestorResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+
+func (m *HasAncestorResponse) GetHasAncestor() bool {
+	if m != nil {
+		return m.HasAncestor
+	}
+	return false
+}
+
+func (m *HasAncestorResponse) GetUnknownCommit() bool {
+	if m != nil {
+		return m.UnknownCommit
+	}
+	return false
+}
+
+func init() {
+	proto.RegisterType((*HasAncestorRequest)(nil), "apipb.HasAncestorRequest")
+	proto.RegisterType((*HasAncestorResponse)(nil), "apipb.HasAncestorResponse")
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// Client API for MaintnerService service
+
+type MaintnerServiceClient interface {
+	// HasAncestor reports whether one commit contains another commit
+	// in its git history.
+	HasAncestor(ctx context.Context, in *HasAncestorRequest, opts ...grpc.CallOption) (*HasAncestorResponse, error)
+}
+
+type maintnerServiceClient struct {
+	cc *grpc.ClientConn
+}
+
+func NewMaintnerServiceClient(cc *grpc.ClientConn) MaintnerServiceClient {
+	return &maintnerServiceClient{cc}
+}
+
+func (c *maintnerServiceClient) HasAncestor(ctx context.Context, in *HasAncestorRequest, opts ...grpc.CallOption) (*HasAncestorResponse, error) {
+	out := new(HasAncestorResponse)
+	err := grpc.Invoke(ctx, "/apipb.MaintnerService/HasAncestor", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// Server API for MaintnerService service
+
+type MaintnerServiceServer interface {
+	// HasAncestor reports whether one commit contains another commit
+	// in its git history.
+	HasAncestor(context.Context, *HasAncestorRequest) (*HasAncestorResponse, error)
+}
+
+func RegisterMaintnerServiceServer(s *grpc.Server, srv MaintnerServiceServer) {
+	s.RegisterService(&_MaintnerService_serviceDesc, srv)
+}
+
+func _MaintnerService_HasAncestor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(HasAncestorRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(MaintnerServiceServer).HasAncestor(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/apipb.MaintnerService/HasAncestor",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(MaintnerServiceServer).HasAncestor(ctx, req.(*HasAncestorRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+var _MaintnerService_serviceDesc = grpc.ServiceDesc{
+	ServiceName: "apipb.MaintnerService",
+	HandlerType: (*MaintnerServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "HasAncestor",
+			Handler:    _MaintnerService_HasAncestor_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "api.proto",
+}
+
+func init() { proto.RegisterFile("api.proto", fileDescriptor0) }
+
+var fileDescriptor0 = []byte{
+	// 194 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4c, 0x2c, 0xc8, 0xd4,
+	0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x4d, 0x2c, 0xc8, 0x2c, 0x48, 0x52, 0xf2, 0xe0, 0x12,
+	0xf2, 0x48, 0x2c, 0x76, 0xcc, 0x4b, 0x4e, 0x2d, 0x2e, 0xc9, 0x2f, 0x0a, 0x4a, 0x2d, 0x2c, 0x4d,
+	0x2d, 0x2e, 0x11, 0x12, 0xe3, 0x62, 0x4b, 0xce, 0xcf, 0xcd, 0xcd, 0x2c, 0x91, 0x60, 0x54, 0x60,
+	0xd4, 0xe0, 0x0c, 0x82, 0xf2, 0x84, 0xa4, 0xb8, 0x38, 0x12, 0xa1, 0x4a, 0x25, 0x98, 0xc0, 0x32,
+	0x70, 0xbe, 0x52, 0x3c, 0x97, 0x30, 0x8a, 0x49, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x42, 0x8a,
+	0x5c, 0x3c, 0x19, 0x89, 0xc5, 0xf1, 0x70, 0x6d, 0x20, 0x03, 0x39, 0x82, 0xb8, 0x33, 0x10, 0x4a,
+	0x85, 0x54, 0xb9, 0xf8, 0x4a, 0xf3, 0xb2, 0xf3, 0xf2, 0xcb, 0xf3, 0xe2, 0xa1, 0xb6, 0x32, 0x81,
+	0x15, 0xf1, 0x42, 0x45, 0x9d, 0xc1, 0x82, 0x46, 0xe1, 0x5c, 0xfc, 0xbe, 0x89, 0x99, 0x79, 0x25,
+	0x79, 0xa9, 0x45, 0xc1, 0xa9, 0x45, 0x65, 0x99, 0xc9, 0xa9, 0x42, 0x2e, 0x5c, 0xdc, 0x48, 0x76,
+	0x0a, 0x49, 0xea, 0x81, 0x3d, 0xa5, 0x87, 0xe9, 0x23, 0x29, 0x29, 0x6c, 0x52, 0x10, 0x27, 0x26,
+	0xb1, 0x81, 0x43, 0xc4, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x74, 0x5e, 0xcf, 0x54, 0x1e, 0x01,
+	0x00, 0x00,
+}
diff --git a/maintner/maintnerd/apipb/api.proto b/maintner/maintnerd/apipb/api.proto
new file mode 100644
index 0000000..4157e9f
--- /dev/null
+++ b/maintner/maintnerd/apipb/api.proto
@@ -0,0 +1,27 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// https://developers.google.com/protocol-buffers/docs/proto3
+syntax = "proto3";
+
+package apipb;
+
+message HasAncestorRequest {
+  string commit = 1;   // full git commit hash (subject of query)
+  string ancestor = 2; // full git commit hash of sought ancestor
+}
+
+message HasAncestorResponse {
+  // has_ancestor is whether ancestor appears in commit's history.
+  bool has_ancestor = 1;
+
+  // unknown_commit is true if the provided commit was unknown.
+  bool unknown_commit = 2;
+}
+
+service MaintnerService {
+  // HasAncestor reports whether one commit contains another commit
+  // in its git history.
+  rpc HasAncestor(HasAncestorRequest) returns (HasAncestorResponse);
+}
diff --git a/maintner/maintnerd/apipb/apipb.go b/maintner/maintnerd/apipb/apipb.go
new file mode 100644
index 0000000..18067c6
--- /dev/null
+++ b/maintner/maintnerd/apipb/apipb.go
@@ -0,0 +1,12 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package apipb
+
+// Run "go generate" in this directory to update. You need to have:
+//
+// - a protoc binary (see https://github.com/golang/protobuf#installation)
+// - go get go4.org/grpc-codegen/protoc-gen-go4grpc
+
+//go:generate protoc --proto_path=$GOPATH/src:. --go4grpc_out=plugins=grpc:. api.proto
diff --git a/maintner/maintnerd/maintnerd.go b/maintner/maintnerd/maintnerd.go
index 41de199..500e22a 100644
--- a/maintner/maintnerd/maintnerd.go
+++ b/maintner/maintnerd/maintnerd.go
@@ -17,23 +17,28 @@
 	"log"
 	"net"
 	"net/http"
+	"net/http/httptest"
 	"os"
 	"path/filepath"
 	"runtime"
+	"strconv"
 	"strings"
 	"time"
 
 	"cloud.google.com/go/compute/metadata"
 	"cloud.google.com/go/storage"
+	"go4.org/grpc"
 	"golang.org/x/build/autocertcache"
 	"golang.org/x/build/gerrit"
 	"golang.org/x/build/maintner"
 	"golang.org/x/build/maintner/godata"
+	"golang.org/x/build/maintner/maintnerd/apipb"
 	"golang.org/x/crypto/acme/autocert"
 )
 
 var (
 	listen         = flag.String("listen", "localhost:6343", "listen address")
+	devTLSPort     = flag.Int("dev-tls-port", 0, "if non-zero, port number to run localhost self-signed TLS server")
 	autocertDomain = flag.String("autocert", "", "if non-empty, listen on port 443 and serve a LetsEncrypt TLS cert on this domain")
 	autocertBucket = flag.String("autocert-bucket", "", "if non-empty, Google Cloud Storage bucket to store LetsEncrypt cache in")
 	syncQuit       = flag.Bool("sync-and-quit", false, "sync once and quit; don't run a server")
@@ -196,7 +201,15 @@
 		corpus.StartPubSubHelperSubscribe(*pubsub)
 	}
 
+	grpcServer := grpc.NewServer()
+	apipb.RegisterMaintnerServiceServer(grpcServer, &apiService{c: corpus})
+	http.Handle("/apipb.MaintnerService/", grpcServer)
+
 	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		if strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") {
+			grpcServer.ServeHTTP(w, r)
+			return
+		}
 		if r.URL.Path != "/" {
 			http.NotFound(w, r)
 			return
@@ -224,7 +237,10 @@
 		go func() { errc <- fmt.Errorf("http.Serve = %v", http.Serve(ln, nil)) }()
 	}
 	if *autocertDomain != "" {
-		go func() { errc <- serveTLS() }()
+		go func() { errc <- serveAutocertTLS() }()
+	}
+	if *devTLSPort != 0 {
+		go func() { errc <- serveDevTLS(*devTLSPort) }()
 	}
 
 	log.Fatal(<-errc)
@@ -287,7 +303,28 @@
 	return token, nil
 }
 
-func serveTLS() error {
+func serveDevTLS(port int) error {
+	ln, err := net.Listen("tcp", "localhost:"+strconv.Itoa(port))
+	if err != nil {
+		return err
+	}
+	defer ln.Close()
+	log.Printf("Serving self-signed TLS at https://%s", ln.Addr())
+	// Abuse httptest for its localhost TLS setup code:
+	ts := httptest.NewUnstartedServer(http.DefaultServeMux)
+	// Ditch the provided listener, replace with our own:
+	ts.Listener.Close()
+	ts.Listener = ln
+	ts.TLS = &tls.Config{
+		NextProtos:         []string{"h2", "http/1.1"},
+		InsecureSkipVerify: true,
+	}
+	ts.StartTLS()
+
+	select {}
+}
+
+func serveAutocertTLS() error {
 	if *autocertBucket == "" {
 		return fmt.Errorf("using --autocert requires --autocert-bucket.")
 	}
@@ -295,6 +332,7 @@
 	if err != nil {
 		return err
 	}
+	defer ln.Close()
 	sc, err := storage.NewClient(context.Background())
 	if err != nil {
 		return fmt.Errorf("storage.NewClient: %v", err)
diff --git a/maintner/maintpb/maintner.pb.go b/maintner/maintpb/maintner.pb.go
index d343df8..69b3e62 100644
--- a/maintner/maintpb/maintner.pb.go
+++ b/maintner/maintpb/maintner.pb.go
@@ -1,6 +1,5 @@
-// Code generated by protoc-gen-go.
+// Code generated by protoc-gen-go4grpc; DO NOT EDIT
 // source: maintner.proto
-// DO NOT EDIT!
 
 /*
 Package maintpb is a generated protocol buffer package.
diff --git a/maintner/maintpb/maintpb.go b/maintner/maintpb/maintpb.go
index af7f668..6951b85 100644
--- a/maintner/maintpb/maintpb.go
+++ b/maintner/maintpb/maintpb.go
@@ -4,11 +4,12 @@
 
 package maintpb
 
-// Run "go generate" in this directory to update. You need to have:
-// - a protoc binary
-// - a protoc-gen-go binary
-// - go get github.com/golang/protobuf
+// Run "go generate" in this directory to update. You need to:
 //
-// See https://github.com/golang/protobuf#installation for more info.
+// - have the protoc binary in your $PATH
+// - go get go4.org/grpc-codegen/protoc-gen-go4grpc
+//
+// See https://github.com/golang/protobuf#installation for how to install
+// the protoc binary.
 
-//go:generate protoc --proto_path=$GOPATH/src:. --go_out=plugins=grpc:. maintner.proto
+//go:generate protoc --proto_path=$GOPATH/src:. --go4grpc_out=plugins=grpc:. maintner.proto
diff --git a/maintner/maintq/maintq.go b/maintner/maintq/maintq.go
new file mode 100644
index 0000000..62f2fbe
--- /dev/null
+++ b/maintner/maintq/maintq.go
@@ -0,0 +1,53 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The maintq command queries a maintnerd gRPC server.
+// This tool is mostly for debugging.
+package main
+
+import (
+	"context"
+	"crypto/tls"
+	"flag"
+	"log"
+	"net/http"
+	"strings"
+
+	"go4.org/grpc"
+	"golang.org/x/build/maintner/maintnerd/apipb"
+	"golang.org/x/net/http2"
+)
+
+var (
+	server = flag.String("server", "maintnerd.golang.org", "maintnerd server")
+)
+
+func main() {
+	flag.Parse()
+
+	tr := &http.Transport{
+		TLSClientConfig: &tls.Config{
+			NextProtos:         []string{"h2"},
+			InsecureSkipVerify: strings.HasPrefix(*server, "localhost:"),
+		},
+	}
+	hc := &http.Client{Transport: tr}
+	http2.ConfigureTransport(tr)
+
+	cc, err := grpc.NewClient(hc, "https://"+*server)
+	if err != nil {
+		log.Fatal(err)
+	}
+	mc := apipb.NewMaintnerServiceClient(cc)
+	ctx := context.Background()
+
+	res, err := mc.HasAncestor(ctx, &apipb.HasAncestorRequest{
+		Commit:   "f700f89b0be0eda0cda20427fbdae4ff1cb7e6a8",
+		Ancestor: "2dc27839df7d51b0544c0ac8b2a0b8f030b7a90c",
+	})
+	if err != nil {
+		log.Fatal(err)
+	}
+	log.Printf("Got: %+v", res)
+}