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 */