diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj
index 76d100e60..739b42a00 100644
--- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj
+++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj
@@ -99,6 +99,7 @@
+
diff --git a/LibGit2Sharp.Tests/SmartSubtransportFixture.cs b/LibGit2Sharp.Tests/SmartSubtransportFixture.cs
new file mode 100644
index 000000000..feecee9cc
--- /dev/null
+++ b/LibGit2Sharp.Tests/SmartSubtransportFixture.cs
@@ -0,0 +1,261 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using LibGit2Sharp.Tests.TestHelpers;
+using Xunit;
+using Xunit.Extensions;
+
+namespace LibGit2Sharp.Tests
+{
+ public class SmartSubtransportFixture : BaseFixture
+ {
+ [Theory]
+ [InlineData("http://github.com/libgit2/TestGitRepository")]
+ [InlineData("https://github.com/libgit2/TestGitRepository")]
+ public void CustomSmartSubtransportTest(string url)
+ {
+ string remoteName = "testRemote";
+
+ var scd = BuildSelfCleaningDirectory();
+ var repoPath = Repository.Init(scd.RootedDirectoryPath);
+
+ using ((IDisposable)LibGit2.RegisterSmartSubtransport(new MockSmartSubtransportRegistration("http://", 2)))
+ using ((IDisposable)LibGit2.RegisterSmartSubtransport(new MockSmartSubtransportRegistration("https://", 2)))
+ {
+ using (var repo = new Repository(scd.DirectoryPath))
+ {
+ Remote remote = repo.Network.Remotes.Add(remoteName, url);
+
+ // Set up structures for the expected results
+ // and verifying the RemoteUpdateTips callback.
+ TestRemoteInfo expectedResults = TestRemoteInfo.TestRemoteInstance;
+ ExpectedFetchState expectedFetchState = new ExpectedFetchState(remoteName);
+
+ // Add expected branch objects
+ foreach (KeyValuePair kvp in expectedResults.BranchTips)
+ {
+ expectedFetchState.AddExpectedBranch(kvp.Key, ObjectId.Zero, kvp.Value);
+ }
+
+ // Add the expected tags
+ string[] expectedTagNames = { "blob", "commit_tree" };
+ foreach (string tagName in expectedTagNames)
+ {
+ TestRemoteInfo.ExpectedTagInfo expectedTagInfo = expectedResults.Tags[tagName];
+ expectedFetchState.AddExpectedTag(tagName, ObjectId.Zero, expectedTagInfo);
+ }
+
+ // Perform the actual fetch
+ repo.Network.Fetch(remote, new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler, TagFetchMode = TagFetchMode.Auto });
+
+ // Verify the expected
+ expectedFetchState.CheckUpdatedReferences(repo);
+ }
+ }
+ }
+
+ private class MockSmartSubtransportRegistration : SmartSubtransportRegistration, IDisposable
+ {
+ public MockSmartSubtransportRegistration(string prefix, int priority)
+ {
+ Prefix = prefix;
+ Priority = priority;
+ }
+
+ public void Dispose()
+ {
+ Free();
+ }
+ }
+
+ [RpcSmartSubtransport]
+ private class MockSmartSubtransport : SmartSubtransport
+ {
+ protected override SmartSubtransportStream Action(String url, GitSmartSubtransportAction action)
+ {
+ String endpointUrl, contentType = null;
+ bool isPost = false;
+
+ switch (action)
+ {
+ case GitSmartSubtransportAction.UploadPackList:
+ endpointUrl = String.Concat(url, "/info/refs?service=git-upload-pack");
+ break;
+
+ case GitSmartSubtransportAction.UploadPack:
+ endpointUrl = String.Concat(url, "/git-upload-pack");
+ contentType = "application/x-git-upload-pack-request";
+ isPost = true;
+ break;
+
+ case GitSmartSubtransportAction.ReceivePackList:
+ endpointUrl = String.Concat(url, "/info/refs?service=git-receive-pack");
+ break;
+
+ case GitSmartSubtransportAction.ReceivePack:
+ endpointUrl = String.Concat(url, "/git-receive-pack");
+ contentType = "application/x-git-receive-pack-request";
+ isPost = true;
+ break;
+
+ default:
+ throw new InvalidOperationException();
+ }
+
+ return new MockSmartSubtransportStream(this, endpointUrl, isPost, contentType);
+ }
+
+ private class MockSmartSubtransportStream : SmartSubtransportStream
+ {
+ private static int MAX_REDIRECTS = 5;
+
+ private MemoryStream postBuffer = new MemoryStream();
+ private Stream responseStream;
+
+ public MockSmartSubtransportStream(MockSmartSubtransport parent, string endpointUrl, bool isPost, string contentType)
+ : base(parent)
+ {
+ EndpointUrl = endpointUrl;
+ IsPost = isPost;
+ ContentType = contentType;
+ }
+
+ private string EndpointUrl
+ {
+ get;
+ set;
+ }
+
+ private bool IsPost
+ {
+ get;
+ set;
+ }
+
+ private string ContentType
+ {
+ get;
+ set;
+ }
+
+ public override int Write(Stream dataStream, long length)
+ {
+ byte[] buffer = new byte[4096];
+ long writeTotal = 0;
+
+ while (length > 0)
+ {
+ int readLen = dataStream.Read(buffer, 0, (int)Math.Min(buffer.Length, length));
+
+ if (readLen == 0)
+ {
+ break;
+ }
+
+ postBuffer.Write(buffer, 0, readLen);
+ length -= readLen;
+ writeTotal += readLen;
+ }
+
+ if (writeTotal < length)
+ {
+ throw new EndOfStreamException("Could not write buffer (short read)");
+ }
+
+ return 0;
+ }
+
+ private static HttpWebRequest CreateWebRequest(string endpointUrl, bool isPost, string contentType)
+ {
+ HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(endpointUrl);
+ webRequest.UserAgent = "git/1.0 (libgit2 custom transport)";
+ webRequest.ServicePoint.Expect100Continue = false;
+ webRequest.AllowAutoRedirect = false;
+
+ if (isPost)
+ {
+ webRequest.Method = "POST";
+ webRequest.ContentType = contentType;
+ }
+
+ return webRequest;
+ }
+
+ private HttpWebResponse GetResponseWithRedirects()
+ {
+ HttpWebRequest request = CreateWebRequest(EndpointUrl, IsPost, ContentType);
+ HttpWebResponse response = null;
+
+ for (int i = 0; i < MAX_REDIRECTS; i++)
+ {
+ if (IsPost && postBuffer.Length > 0)
+ {
+ postBuffer.Seek(0, SeekOrigin.Begin);
+
+ using (Stream requestStream = request.GetRequestStream())
+ {
+ postBuffer.WriteTo(requestStream);
+ }
+ }
+
+ response = (HttpWebResponse)request.GetResponse();
+
+ if (response.StatusCode == HttpStatusCode.Moved || response.StatusCode == HttpStatusCode.Redirect)
+ {
+ request = CreateWebRequest(response.Headers["Location"], IsPost, ContentType);
+ continue;
+ }
+
+ break;
+ }
+
+ if (response == null)
+ {
+ throw new Exception("Too many redirects");
+ }
+
+ return response;
+ }
+
+ public override int Read(Stream dataStream, long length, out long readTotal)
+ {
+ byte[] buffer = new byte[4096];
+ readTotal = 0;
+
+ if (responseStream == null)
+ {
+ HttpWebResponse response = GetResponseWithRedirects();
+ responseStream = response.GetResponseStream();
+ }
+
+ while (length > 0)
+ {
+ int readLen = responseStream.Read(buffer, 0, (int)Math.Min(buffer.Length, length));
+
+ if (readLen == 0)
+ break;
+
+ dataStream.Write(buffer, 0, readLen);
+ readTotal += readLen;
+ length -= readLen;
+ }
+
+ return 0;
+ }
+
+ protected override void Dispose()
+ {
+ if (responseStream != null)
+ {
+ responseStream.Dispose();
+ responseStream = null;
+ }
+
+ base.Dispose();
+ }
+ }
+ }
+ }
+}
diff --git a/LibGit2Sharp/Core/GitSmartSubtransport.cs b/LibGit2Sharp/Core/GitSmartSubtransport.cs
new file mode 100644
index 000000000..d9b3c7545
--- /dev/null
+++ b/LibGit2Sharp/Core/GitSmartSubtransport.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace LibGit2Sharp.Core
+{
+ [StructLayout(LayoutKind.Sequential)]
+ internal class GitSmartSubtransport
+ {
+ static GitSmartSubtransport()
+ {
+ GCHandleOffset = Marshal.OffsetOf(typeof(GitSmartSubtransport), "GCHandle").ToInt32();
+ }
+
+ public action_callback Action;
+ public close_callback Close;
+ public free_callback Free;
+
+ /* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */
+
+ public IntPtr GCHandle;
+
+ /* The following static fields are not part of the structure definition. */
+
+ public static int GCHandleOffset;
+
+ public delegate int action_callback(
+ out IntPtr stream,
+ IntPtr subtransport,
+ IntPtr url,
+ GitSmartSubtransportAction action);
+
+ public delegate int close_callback(IntPtr subtransport);
+
+ public delegate void free_callback(IntPtr subtransport);
+ }
+}
diff --git a/LibGit2Sharp/Core/GitSmartSubtransportRegistration.cs b/LibGit2Sharp/Core/GitSmartSubtransportRegistration.cs
new file mode 100644
index 000000000..724c6c414
--- /dev/null
+++ b/LibGit2Sharp/Core/GitSmartSubtransportRegistration.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace LibGit2Sharp.Core
+{
+ [StructLayout(LayoutKind.Sequential)]
+ internal class GitSmartSubtransportRegistration
+ {
+ public IntPtr SubtransportCallback;
+ public uint Rpc;
+
+ public delegate int create_callback(
+ out IntPtr subtransport,
+ IntPtr transport);
+ }
+}
diff --git a/LibGit2Sharp/Core/GitSmartSubtransportStream.cs b/LibGit2Sharp/Core/GitSmartSubtransportStream.cs
new file mode 100644
index 000000000..f73218c09
--- /dev/null
+++ b/LibGit2Sharp/Core/GitSmartSubtransportStream.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace LibGit2Sharp.Core
+{
+ [StructLayout(LayoutKind.Sequential)]
+ internal class GitSmartSubtransportStream
+ {
+ static GitSmartSubtransportStream()
+ {
+ GCHandleOffset = Marshal.OffsetOf(typeof(GitSmartSubtransportStream), "GCHandle").ToInt32();
+ }
+
+ public IntPtr SmartTransport;
+
+ public read_callback Read;
+ public write_callback Write;
+ public free_callback Free;
+
+ /* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */
+
+ public IntPtr GCHandle;
+
+ /* The following static fields are not part of the structure definition. */
+
+ public static int GCHandleOffset;
+
+ public delegate int read_callback(
+ IntPtr stream,
+ IntPtr buffer,
+ UIntPtr buf_size,
+ out UIntPtr bytes_read);
+
+ public delegate int write_callback(
+ IntPtr stream,
+ IntPtr buffer,
+ UIntPtr len);
+
+ public delegate void free_callback(
+ IntPtr stream);
+ }
+}
diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs
index 0653503bd..40214441f 100644
--- a/LibGit2Sharp/Core/NativeMethods.cs
+++ b/LibGit2Sharp/Core/NativeMethods.cs
@@ -1396,6 +1396,26 @@ internal static extern int git_tag_delete(
internal delegate int git_transfer_progress_callback(ref GitTransferProgress stats, IntPtr payload);
+ internal delegate int git_transport_cb(out IntPtr transport, IntPtr remote, IntPtr payload);
+
+ [DllImport(libgit2)]
+ internal static extern int git_transport_register(
+ [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix,
+ uint priority,
+ IntPtr transport_cb,
+ IntPtr payload);
+
+ [DllImport(libgit2)]
+ internal static extern int git_transport_smart(
+ out IntPtr transport,
+ IntPtr remote,
+ IntPtr definition);
+
+ [DllImport(libgit2)]
+ internal static extern void git_transport_unregister(
+ [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix,
+ uint priority);
+
[DllImport(libgit2)]
internal static extern uint git_tree_entry_filemode(SafeHandle entry);
diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs
index e698d971b..ba970461d 100644
--- a/LibGit2Sharp/Core/Proxy.cs
+++ b/LibGit2Sharp/Core/Proxy.cs
@@ -2620,6 +2620,20 @@ public static GitObjectType git_tag_target_type(GitObjectSafeHandle tag)
#endregion
+ #region git_transport_
+
+ public static void git_transport_register(String prefix, uint priority, IntPtr transport_cb, IntPtr param)
+ {
+ Ensure.ZeroResult(NativeMethods.git_transport_register(prefix, priority, transport_cb, param));
+ }
+
+ public static void git_transport_unregister(String prefix, uint priority)
+ {
+ NativeMethods.git_transport_unregister(prefix, priority);
+ }
+
+ #endregion
+
#region git_tree_
public static Mode git_tree_entry_attributes(SafeHandle entry)
diff --git a/LibGit2Sharp/LibGit2.cs b/LibGit2Sharp/LibGit2.cs
new file mode 100644
index 000000000..07e3d5700
--- /dev/null
+++ b/LibGit2Sharp/LibGit2.cs
@@ -0,0 +1,55 @@
+using System;
+using LibGit2Sharp.Core;
+
+namespace LibGit2Sharp
+{
+ ///
+ /// Methods that affect the entirety of the Git implementation in libgit2 and
+ /// LibGit2Sharp.
+ ///
+ public sealed class LibGit2
+ {
+ ///
+ /// Registers a new as a custom
+ /// smart-protocol transport with libgit2.
+ ///
+ /// The type of SmartSubtransport to register
+ /// The prefix (i.e. "http://") to register
+ /// The priority of the transport; the value must be 2 or larger to override a built-in transport
+ public static SmartSubtransportRegistration RegisterSmartSubtransport(string prefix, int priority)
+ where T : SmartSubtransport, new()
+ {
+ var registration = new SmartSubtransportRegistration(prefix, priority);
+
+ return RegisterSmartSubtransport(registration);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static SmartSubtransportRegistration RegisterSmartSubtransport(SmartSubtransportRegistration registration)
+ where T : SmartSubtransport, new()
+ {
+ Ensure.ArgumentNotNull(registration, "registration");
+
+ try
+ {
+ Proxy.git_transport_register(
+ registration.Prefix,
+ (uint)registration.Priority,
+ registration.FunctionPointer,
+ registration.RegistrationPointer);
+ }
+ catch (Exception)
+ {
+ registration.Free();
+ throw;
+ }
+
+ return registration;
+ }
+ }
+}
diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj
index e3c3ba0f7..fd5ee54f0 100644
--- a/LibGit2Sharp/LibGit2Sharp.csproj
+++ b/LibGit2Sharp/LibGit2Sharp.csproj
@@ -84,6 +84,7 @@
+
@@ -173,6 +174,7 @@
+
@@ -200,6 +202,11 @@
+
+
+
+
+
diff --git a/LibGit2Sharp/SmartSubtransport.cs b/LibGit2Sharp/SmartSubtransport.cs
new file mode 100644
index 000000000..6395139d8
--- /dev/null
+++ b/LibGit2Sharp/SmartSubtransport.cs
@@ -0,0 +1,184 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+using LibGit2Sharp.Core;
+
+namespace LibGit2Sharp
+{
+ ///
+ /// When applied to a subclass of SmartSubtransport, indicates that this
+ /// custom subtransport has RPC semantics (request/response). HTTP falls into
+ /// this category; the git:// protocol, which uses a raw socket, does not.
+ ///
+ [AttributeUsage(AttributeTargets.Class)]
+ public class RpcSmartSubtransportAttribute : Attribute
+ {
+ }
+
+ ///
+ /// An enumeration of the type of connections which a "smart" subtransport
+ /// may be asked to create on behalf of libgit2.
+ ///
+ public enum GitSmartSubtransportAction
+ {
+ ///
+ /// For HTTP, this indicates a GET to /info/refs?service=git-upload-pack
+ ///
+ UploadPackList = 1,
+
+ ///
+ /// For HTTP, this indicates a POST to /git-upload-pack
+ ///
+ UploadPack = 2,
+
+ ///
+ /// For HTTP, this indicates a GET to /info/refs?service=git-receive-pack
+ ///
+ ReceivePackList = 3,
+
+ ///
+ /// For HTTP, this indicates a POST to /git-receive-pack
+ ///
+ ReceivePack = 4
+ }
+
+ ///
+ /// Base class for custom subtransports for the "smart" transport.
+ ///
+ public abstract class SmartSubtransport
+ {
+ ///
+ /// Invoked by libgit2 to create a connection using this subtransport.
+ ///
+ /// The endpoint to connect to
+ /// The type of connection to create
+ /// A SmartSubtransportStream representing the connection
+ protected abstract SmartSubtransportStream Action(String url, GitSmartSubtransportAction action);
+
+ ///
+ /// Invoked by libgit2 when this subtransport is no longer needed, but may be re-used in the future.
+ /// Override this method to add additional cleanup steps to your subclass. Be sure to call base.Close().
+ ///
+ protected virtual void Close()
+ {
+ }
+
+ ///
+ /// Invoked by libgit2 when this subtransport is being freed. Override this method to add additional
+ /// cleanup steps to your subclass. Be sure to call base.Dispose().
+ ///
+ protected virtual void Dispose()
+ {
+ Close();
+
+ if (IntPtr.Zero != nativeSubtransportPointer)
+ {
+ GCHandle.FromIntPtr(Marshal.ReadIntPtr(nativeSubtransportPointer, GitSmartSubtransport.GCHandleOffset)).Free();
+ Marshal.FreeHGlobal(nativeSubtransportPointer);
+ nativeSubtransportPointer = IntPtr.Zero;
+ }
+ }
+
+ private IntPtr nativeSubtransportPointer;
+
+ internal IntPtr GitSmartSubtransportPointer
+ {
+ get
+ {
+ if (IntPtr.Zero == nativeSubtransportPointer)
+ {
+ var nativeTransport = new GitSmartSubtransport();
+
+ nativeTransport.Action = EntryPoints.ActionCallback;
+ nativeTransport.Close = EntryPoints.CloseCallback;
+ nativeTransport.Free = EntryPoints.FreeCallback;
+
+ nativeTransport.GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(this));
+ nativeSubtransportPointer = Marshal.AllocHGlobal(Marshal.SizeOf(nativeTransport));
+ Marshal.StructureToPtr(nativeTransport, nativeSubtransportPointer, false);
+ }
+
+ return nativeSubtransportPointer;
+ }
+ }
+
+ private static class EntryPoints
+ {
+ // Because our GitSmartSubtransport structure exists on the managed heap only for a short time (to be marshaled
+ // to native memory with StructureToPtr), we need to bind to static delegates. If at construction time
+ // we were to bind to the methods directly, that's the same as newing up a fresh delegate every time.
+ // Those delegates won't be rooted in the object graph and can be collected as soon as StructureToPtr finishes.
+ public static GitSmartSubtransport.action_callback ActionCallback = new GitSmartSubtransport.action_callback(Action);
+ public static GitSmartSubtransport.close_callback CloseCallback = new GitSmartSubtransport.close_callback(Close);
+ public static GitSmartSubtransport.free_callback FreeCallback = new GitSmartSubtransport.free_callback(Free);
+
+ private static int Action(
+ out IntPtr stream,
+ IntPtr subtransport,
+ IntPtr url,
+ GitSmartSubtransportAction action)
+ {
+ stream = IntPtr.Zero;
+
+ SmartSubtransport t = GCHandle.FromIntPtr(Marshal.ReadIntPtr(subtransport, GitSmartSubtransport.GCHandleOffset)).Target as SmartSubtransport;
+ String urlAsString = LaxUtf8Marshaler.FromNative(url);
+
+ if (null != t &&
+ !String.IsNullOrEmpty(urlAsString))
+ {
+ try
+ {
+ stream = t.Action(urlAsString, action).GitSmartTransportStreamPointer;
+
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ Proxy.giterr_set_str(GitErrorCategory.Net, ex);
+ }
+ }
+
+ return (int)GitErrorCode.Error;
+ }
+
+ private static int Close(IntPtr subtransport)
+ {
+ SmartSubtransport t = GCHandle.FromIntPtr(Marshal.ReadIntPtr(subtransport, GitSmartSubtransport.GCHandleOffset)).Target as SmartSubtransport;
+
+ if (null != t)
+ {
+ try
+ {
+ t.Close();
+
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ Proxy.giterr_set_str(GitErrorCategory.Net, ex);
+ }
+ }
+
+ return (int)GitErrorCode.Error;
+ }
+
+ private static void Free(IntPtr subtransport)
+ {
+ SmartSubtransport t = GCHandle.FromIntPtr(Marshal.ReadIntPtr(subtransport, GitSmartSubtransport.GCHandleOffset)).Target as SmartSubtransport;
+
+ if (null != t)
+ {
+ try
+ {
+ t.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Proxy.giterr_set_str(GitErrorCategory.Net, ex);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/LibGit2Sharp/SmartSubtransportRegistration.cs b/LibGit2Sharp/SmartSubtransportRegistration.cs
new file mode 100644
index 000000000..4e34ba2dc
--- /dev/null
+++ b/LibGit2Sharp/SmartSubtransportRegistration.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Linq;
+using System.Runtime.InteropServices;
+using LibGit2Sharp.Core;
+
+namespace LibGit2Sharp
+{
+ ///
+ /// An object representing the registration of a SmartSubtransport type with libgit2
+ /// under a particular prefix (i.e. "http://") and priority.
+ ///
+ /// The type of SmartSubtransport to register
+ public class SmartSubtransportRegistration
+ where T : SmartSubtransport, new()
+ {
+ ///
+ /// Needed for mocking purposes.
+ ///
+ protected SmartSubtransportRegistration()
+ {
+ }
+
+ ///
+ /// Creates a new native registration for a smart protocol transport
+ /// in libgit2.
+ ///
+ /// The prefix (i.e. "http://") to register
+ /// The priority of the transport; the value must be 2 or larger to override a built-in transport
+ internal SmartSubtransportRegistration(string prefix, int priority)
+ {
+ Ensure.ArgumentNotNull(prefix, "prefix");
+ Ensure.ArgumentConformsTo(priority, s => s >= 0, "priority");
+
+ Prefix = prefix;
+ Priority = priority;
+ RegistrationPointer = CreateRegistrationPointer();
+ FunctionPointer = CreateFunctionPointer();
+ }
+
+ ///
+ /// The URI prefix (ie "http://") for this transport.
+ ///
+ public virtual string Prefix
+ {
+ get;
+ protected set;
+ }
+
+ ///
+ /// The priority of this transport relative to others
+ ///
+ public virtual int Priority
+ {
+ get;
+ protected set;
+ }
+
+ internal IntPtr RegistrationPointer
+ {
+ get;
+ private set;
+ }
+
+ internal IntPtr FunctionPointer
+ {
+ get;
+ private set;
+ }
+
+ private IntPtr CreateRegistrationPointer()
+ {
+ var registration = new GitSmartSubtransportRegistration();
+
+ registration.SubtransportCallback = Marshal.GetFunctionPointerForDelegate(EntryPoints.SubtransportCallback);
+
+ if (typeof(T).GetCustomAttributes(true).Any(s => s.GetType() == typeof(RpcSmartSubtransportAttribute)))
+ {
+ registration.Rpc = 1;
+ }
+
+ var registrationPointer = Marshal.AllocHGlobal(Marshal.SizeOf(registration));
+ Marshal.StructureToPtr(registration, registrationPointer, false);
+
+ return registrationPointer;
+ }
+
+ private IntPtr CreateFunctionPointer()
+ {
+ return Marshal.GetFunctionPointerForDelegate(EntryPoints.TransportCallback);
+ }
+
+ protected internal void Free()
+ {
+ Proxy.git_transport_unregister(Prefix, (uint)Priority);
+ Marshal.FreeHGlobal(RegistrationPointer);
+ }
+
+ private static class EntryPoints
+ {
+ // Because our GitSmartSubtransportRegistration structure exists on the managed heap only for a short time (to be marshaled
+ // to native memory with StructureToPtr), we need to bind to static delegates. If at construction time
+ // we were to bind to the methods directly, that's the same as newing up a fresh delegate every time.
+ // Those delegates won't be rooted in the object graph and can be collected as soon as StructureToPtr finishes.
+ public static GitSmartSubtransportRegistration.create_callback SubtransportCallback = new GitSmartSubtransportRegistration.create_callback(Subtransport);
+ public static NativeMethods.git_transport_cb TransportCallback = new NativeMethods.git_transport_cb(Transport);
+
+ private static int Subtransport(
+ out IntPtr subtransport,
+ IntPtr transport)
+ {
+ subtransport = IntPtr.Zero;
+
+ try
+ {
+ subtransport = new T().GitSmartSubtransportPointer;
+
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ Proxy.giterr_set_str(GitErrorCategory.Net, ex);
+ }
+
+ return (int)GitErrorCode.Error;
+ }
+
+ private static int Transport(
+ out IntPtr transport,
+ IntPtr remote,
+ IntPtr payload)
+ {
+ transport = IntPtr.Zero;
+
+ try
+ {
+ return NativeMethods.git_transport_smart(out transport, remote, payload);
+ }
+ catch (Exception ex)
+ {
+ Proxy.giterr_set_str(GitErrorCategory.Net, ex);
+ }
+
+ return (int)GitErrorCode.Error;
+ }
+ }
+ }
+}
diff --git a/LibGit2Sharp/SmartSubtransportStream.cs b/LibGit2Sharp/SmartSubtransportStream.cs
new file mode 100644
index 000000000..6cd24998d
--- /dev/null
+++ b/LibGit2Sharp/SmartSubtransportStream.cs
@@ -0,0 +1,190 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using LibGit2Sharp.Core;
+
+namespace LibGit2Sharp
+{
+ ///
+ /// A stream that represents a two-way connection (socket) for a SmartSubtransport.
+ ///
+ public abstract class SmartSubtransportStream
+ {
+ ///
+ /// This is to quiet the MetaFixture.TypesInLibGit2SharpMustBeExtensibleInATestingContext test.
+ /// Do not use this constructor.
+ ///
+ protected internal SmartSubtransportStream()
+ {
+ throw new InvalidOperationException();
+ }
+
+ ///
+ /// Base constructor for SmartTransportStream. Make sure that your derived class calls this base constructor.
+ ///
+ /// The subtransport that this stream represents a connection over.
+ protected SmartSubtransportStream(SmartSubtransport subtransport)
+ {
+ this.subtransport = subtransport;
+ }
+
+ ///
+ /// Invoked by libgit2 when this stream is no longer needed.
+ /// Override this method to add additional cleanup steps to your subclass. Be sure
+ /// to call base.Dispose().
+ ///
+ protected virtual void Dispose()
+ {
+ if (IntPtr.Zero != nativeStreamPointer)
+ {
+ GCHandle.FromIntPtr(Marshal.ReadIntPtr(nativeStreamPointer, GitSmartSubtransportStream.GCHandleOffset)).Free();
+ Marshal.FreeHGlobal(nativeStreamPointer);
+ nativeStreamPointer = IntPtr.Zero;
+ }
+ }
+
+ ///
+ /// Requests that the stream write the next length bytes of the stream to the provided Stream object.
+ ///
+ public abstract int Read(
+ Stream dataStream,
+ long length,
+ out long bytesRead);
+
+ ///
+ /// Requests that the stream write the first length bytes of the provided Stream object to the stream.
+ ///
+ public abstract int Write(
+ Stream dataStream,
+ long length);
+
+ ///
+ /// The smart transport that this stream represents a connection over.
+ ///
+ public virtual SmartSubtransport SmartTransport
+ {
+ get
+ {
+ return this.subtransport;
+ }
+ }
+
+ private SmartSubtransport subtransport;
+ private IntPtr nativeStreamPointer;
+
+ internal IntPtr GitSmartTransportStreamPointer
+ {
+ get
+ {
+ if (IntPtr.Zero == nativeStreamPointer)
+ {
+ var nativeTransportStream = new GitSmartSubtransportStream();
+
+ nativeTransportStream.SmartTransport = this.subtransport.GitSmartSubtransportPointer;
+ nativeTransportStream.Read = EntryPoints.ReadCallback;
+ nativeTransportStream.Write = EntryPoints.WriteCallback;
+ nativeTransportStream.Free = EntryPoints.FreeCallback;
+
+ nativeTransportStream.GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(this));
+ nativeStreamPointer = Marshal.AllocHGlobal(Marshal.SizeOf(nativeTransportStream));
+ Marshal.StructureToPtr(nativeTransportStream, nativeStreamPointer, false);
+ }
+
+ return nativeStreamPointer;
+ }
+ }
+
+ private static class EntryPoints
+ {
+ // Because our GitSmartSubtransportStream structure exists on the managed heap only for a short time (to be marshaled
+ // to native memory with StructureToPtr), we need to bind to static delegates. If at construction time
+ // we were to bind to the methods directly, that's the same as newing up a fresh delegate every time.
+ // Those delegates won't be rooted in the object graph and can be collected as soon as StructureToPtr finishes.
+ public static GitSmartSubtransportStream.read_callback ReadCallback = new GitSmartSubtransportStream.read_callback(Read);
+ public static GitSmartSubtransportStream.write_callback WriteCallback = new GitSmartSubtransportStream.write_callback(Write);
+ public static GitSmartSubtransportStream.free_callback FreeCallback = new GitSmartSubtransportStream.free_callback(Free);
+
+ private unsafe static int Read(
+ IntPtr stream,
+ IntPtr buffer,
+ UIntPtr buf_size,
+ out UIntPtr bytes_read)
+ {
+ bytes_read = UIntPtr.Zero;
+
+ SmartSubtransportStream transportStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream;
+
+ if (transportStream != null &&
+ buf_size.ToUInt64() < (ulong)long.MaxValue)
+ {
+ using (UnmanagedMemoryStream memoryStream = new UnmanagedMemoryStream((byte*)buffer, 0, (long)buf_size.ToUInt64(), FileAccess.ReadWrite))
+ {
+ try
+ {
+ long longBytesRead;
+
+ int toReturn = transportStream.Read(memoryStream, (long)buf_size.ToUInt64(), out longBytesRead);
+
+ bytes_read = new UIntPtr((ulong)Math.Max(0, longBytesRead));
+
+ return toReturn;
+ }
+ catch (Exception ex)
+ {
+ Proxy.giterr_set_str(GitErrorCategory.Net, ex);
+ }
+ }
+ }
+
+ return (int)GitErrorCode.Error;
+ }
+
+ private static unsafe int Write(
+ IntPtr stream,
+ IntPtr buffer,
+ UIntPtr len)
+ {
+ SmartSubtransportStream transportStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream;
+
+ if (transportStream != null &&
+ len.ToUInt64() < (ulong)long.MaxValue)
+ {
+ long length = (long)len.ToUInt64();
+
+ using (UnmanagedMemoryStream dataStream = new UnmanagedMemoryStream((byte*)buffer, length))
+ {
+ try
+ {
+ return transportStream.Write(dataStream, length);
+ }
+ catch (Exception ex)
+ {
+ Proxy.giterr_set_str(GitErrorCategory.Net, ex);
+ }
+ }
+ }
+
+ return (int)GitErrorCode.Error;
+ }
+
+ private static void Free(
+ IntPtr stream)
+ {
+ SmartSubtransportStream transportStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream;
+
+ if (transportStream != null)
+ {
+ try
+ {
+ transportStream.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Proxy.giterr_set_str(GitErrorCategory.Net, ex);
+ }
+ }
+ }
+ }
+ }
+}
+