Merge remote-tracking branch 'upstream/master' into cmn/tls-stream
diff --git a/git.go b/git.go
index 5181b8b..deb47db 100644
--- a/git.go
+++ b/git.go
@@ -129,6 +129,10 @@
 		panic("libgit2 was not built with threading support")
 	}
 
+	if err := RegisterManagedTls(); err != nil {
+		panic(err)
+	}
+
 	// This is not something we should be doing, as we may be
 	// stomping all over someone else's setup. The user should do
 	// this themselves or use some binding/wrapper which does it
diff --git a/remote.go b/remote.go
index d3f437d..98e35cd 100644
--- a/remote.go
+++ b/remote.go
@@ -10,6 +10,7 @@
 import "C"
 import (
 	"crypto/x509"
+	"errors"
 	"reflect"
 	"runtime"
 	"strings"
@@ -163,6 +164,20 @@
 	Hostkey HostkeyCertificate
 }
 
+func (self *Certificate) toC() (*C.git_cert, error) {
+	switch self.Kind {
+	case CertificateX509:
+		ccert := (*C.git_cert_x509)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_cert_x509{}))))
+		ccert.parent.cert_type = C.GIT_CERT_X509
+		rawCert := self.X509.Raw
+		ccert.len = C.size_t(len(rawCert))
+		ccert.data = C.CBytes(rawCert)
+		return (*C.git_cert)(unsafe.Pointer(ccert)), nil
+	default:
+		return nil, errors.New("not supported")
+	}
+}
+
 type HostkeyKind uint
 
 const (
@@ -734,13 +749,12 @@
 	var cproxy C.git_proxy_options
 	populateProxyOptions(&cproxy, proxyOpts)
 	defer freeProxyOptions(&cproxy)
-	
+
 	cheaders := C.git_strarray{}
 	cheaders.count = C.size_t(len(headers))
 	cheaders.strings = makeCStringsFromStrings(headers)
 	defer freeStrarray(&cheaders)
 
-
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
diff --git a/stream.go b/stream.go
new file mode 100644
index 0000000..8fb0b89
--- /dev/null
+++ b/stream.go
@@ -0,0 +1,251 @@
+package git
+
+/*
+#include <git2.h>
+#include <git2/sys/stream.h>
+
+typedef struct {
+	git_stream parent;
+	void *ptr;
+} managed_stream;
+
+extern int _go_git_register_tls(void);
+extern void _go_git_setup_stream(managed_stream* s, int encrypted, int proxy_support, void *ptr);
+
+*/
+import "C"
+
+import (
+	"crypto/tls"
+	"errors"
+	"fmt"
+	"io"
+	"reflect"
+	"runtime"
+	"unsafe"
+)
+
+// Network stream for libgit2 to use
+type Stream interface {
+	Encrypted() bool
+	ProxySupport() bool
+	Connect() error
+	Certificate() (Certificate, error)
+	SetProxy(ProxyOptions) error
+	io.ReadWriteCloser
+}
+
+type ManagedStream struct {
+	host string
+	port string
+	conn *tls.Conn
+}
+
+func (self *ManagedStream) Encrypted() bool {
+	return true
+}
+
+func (self *ManagedStream) ProxySupport() bool {
+	return false
+}
+
+func (self *ManagedStream) Connect() error {
+	conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%s", self.host, self.port), nil)
+	if err != nil {
+		return err
+	}
+
+	self.conn = conn
+	return nil
+}
+
+func (self *ManagedStream) Certificate() (Certificate, error) {
+	connState := self.conn.ConnectionState()
+	cert := Certificate{
+		Kind: CertificateX509,
+		X509: connState.PeerCertificates[0],
+	}
+
+	return cert, nil
+}
+
+func (self *ManagedStream) SetProxy(opts ProxyOptions) error {
+	return errors.New("proxy not supported")
+}
+
+func (self *ManagedStream) Read(p []byte) (int, error) {
+	return self.conn.Read(p)
+}
+
+func (self *ManagedStream) Write(p []byte) (int, error) {
+	return self.conn.Write(p)
+}
+
+func (self *ManagedStream) Close() error {
+	return self.conn.Close()
+}
+
+var errNotStream = errors.New("passed object does not implement Stream")
+
+// getStreamInterface extracts the Stream interface from the pointers we passed
+// to the C code.
+func getStreamInterface(_s *C.git_stream) (Stream, error) {
+	// For type compatibility we accept C.git_stream but we know we pass
+	// C.managed_stream so force the casting to that.
+	wrapperPtr := (*C.managed_stream)(unsafe.Pointer(_s))
+
+	// Inside we've stored a handle to the actual type, which must implement
+	// Stream.
+	stream, ok := pointerHandles.Get(wrapperPtr.ptr).(Stream)
+	if !ok {
+		return nil, errNotStream
+	}
+
+	return stream, nil
+}
+
+//export streamCertificate
+func streamCertificate(out **C.git_cert, _s *C.git_stream) C.int {
+	stream, err := getStreamInterface(_s)
+	if err != nil {
+		return setLibgit2Error(err)
+	}
+
+	cert, err := stream.Certificate()
+	if err != nil {
+		return setLibgit2Error(err)
+	}
+
+	ccert, err := cert.toC()
+	if err != nil {
+		return setLibgit2Error(err)
+	}
+
+	*out = ccert
+	return 0
+}
+
+//export streamSetProxy
+func streamSetProxy(s *C.git_stream, proxy_opts *C.git_proxy_options) C.int {
+	setLibgit2Error(errors.New("proxy not supported"))
+	return -1
+}
+
+//export streamConnect
+func streamConnect(_s *C.git_stream) C.int {
+	stream, err := getStreamInterface(_s)
+	if err != nil {
+		return setLibgit2Error(err)
+	}
+
+	err = stream.Connect()
+	if err != nil {
+		return setLibgit2Error(err)
+	}
+
+	return 0
+}
+
+//export streamRead
+func streamRead(_s *C.git_stream, data unsafe.Pointer, l C.size_t) C.ssize_t {
+	stream, err := getStreamInterface(_s)
+	if err != nil {
+		setLibgit2Error(err)
+		return -1
+	}
+
+	var p []byte
+	header := (*reflect.SliceHeader)(unsafe.Pointer(&p))
+	header.Cap = int(l)
+	header.Len = int(l)
+	header.Data = uintptr(data)
+
+	n, err := stream.Read(p)
+	if err != nil {
+		setLibgit2Error(err)
+		return -1
+	}
+
+	return C.ssize_t(n)
+}
+
+//export streamWrite
+func streamWrite(_s *C.git_stream, data unsafe.Pointer, l C.size_t, _f C.int) C.ssize_t {
+	stream, err := getStreamInterface(_s)
+	if err != nil {
+		setLibgit2Error(err)
+		return -1
+	}
+
+	var p []byte
+	header := (*reflect.SliceHeader)(unsafe.Pointer(&p))
+	header.Cap = int(l)
+	header.Len = int(l)
+	header.Data = uintptr(data)
+
+	n, err := stream.Write(p)
+	if err != nil {
+		setLibgit2Error(err)
+		return -1
+	}
+
+	return C.ssize_t(n)
+}
+
+//export streamClose
+func streamClose(_s *C.git_stream) C.int {
+	stream, err := getStreamInterface(_s)
+	if err != nil {
+		return setLibgit2Error(err)
+	}
+
+	err = stream.Close()
+	if err != nil {
+		return setLibgit2Error(err)
+	}
+
+	return 0
+}
+
+//export streamFree
+func streamFree(_s *C.git_stream) {
+	wrapperPtr := (*C.managed_stream)(unsafe.Pointer(_s))
+	pointerHandles.Untrack(wrapperPtr.ptr)
+}
+
+func newManagedStream(host, port string) *ManagedStream {
+	return &ManagedStream{
+		host: host,
+		port: port,
+	}
+}
+
+//export streamCallbackCb
+func streamCallbackCb(out **C.git_stream, chost, cport *C.char) C.int {
+	stream := C.calloc(1, C.size_t(unsafe.Sizeof(C.managed_stream{})))
+	managed := newManagedStream(C.GoString(chost), C.GoString(cport))
+	managedPtr := pointerHandles.Track(managed)
+	C._go_git_setup_stream(stream, 1, 0, managedPtr)
+
+	*out = (*C.git_stream)(stream)
+	return 0
+}
+
+func setLibgit2Error(err error) C.int {
+	cstr := C.CString(err.Error())
+	defer C.free(unsafe.Pointer(cstr))
+	C.giterr_set_str(C.GITERR_NET, cstr)
+
+	return -1
+}
+
+func RegisterManagedTls() error {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	if err := C._go_git_register_tls(); err != 0 {
+		return MakeGitError(err)
+	}
+
+	return nil
+}
diff --git a/wrapper.c b/wrapper.c
index 11c2f32..746ae45 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -180,4 +180,27 @@
 	stream->free(stream);
 }
 
+int _go_git_register_tls(void)
+{
+	return git_stream_register_tls((git_stream_cb) streamCallbackCb);
+}
+
+typedef int (*set_proxy_cb)(struct git_stream *, const git_proxy_options *proxy_opts);
+typedef ssize_t (*write_cb)(struct git_stream *, const char *, size_t, int);
+
+void _go_git_setup_stream(managed_stream* s, int encrypted, int proxy_support, void *ptr)
+{
+  s->parent.version = GIT_STREAM_VERSION;
+  s->parent.encrypted = encrypted;
+  s->parent.proxy_support = proxy_support;
+  s->parent.connect = streamConnect;
+  s->parent.certificate = streamCertificate;
+  s->parent.set_proxy = (set_proxy_cb) streamSetProxy;
+  s->parent.read = streamRead;
+  s->parent.write = (write_cb) streamWrite;
+  s->parent.close = streamClose;
+  s->parent.free = streamFree;
+  s->ptr = ptr;
+}
+
 /* EOF */