blob: 8fb0b890abed078abb603c6eeb441d3ec97ce819 [file] [log] [blame]
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
}