Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions git.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ func init() {
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
Expand Down
18 changes: 16 additions & 2 deletions remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ extern void _go_git_setup_callbacks(git_remote_callbacks *callbacks);
import "C"
import (
"crypto/x509"
"errors"
"reflect"
"runtime"
"strings"
Expand Down Expand Up @@ -163,6 +164,20 @@ type Certificate struct {
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 (
Expand Down Expand Up @@ -734,13 +749,12 @@ func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks,
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()

Expand Down
251 changes: 251 additions & 0 deletions stream.go
Original file line number Diff line number Diff line change
@@ -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
}
23 changes: 23 additions & 0 deletions wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,27 @@ void _go_git_writestream_free(git_writestream *stream)
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 */