Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support to compile this lib for windows #163

Open
insinfo opened this issue May 13, 2022 · 3 comments
Open

add support to compile this lib for windows #163

insinfo opened this issue May 13, 2022 · 3 comments

Comments

@insinfo
Copy link

insinfo commented May 13, 2022

add support to compile this lib for windows

@jbkempf
Copy link
Contributor

jbkempf commented May 13, 2022

Why?

@insinfo
Copy link
Author

insinfo commented May 16, 2022

@jbkempf
I was wanting to use this lib in some Flutter/Dart projects for Windows and Linux with dart FFI, as a temporary solution I made a shared library in C# using SMBLibrary and compiled it with .NET 7 NativeAOT to be able to use it in dart with FFI

Basically the project I'm working on needs to access backup file servers that use SMB and copy some files to another server via SFTP in order to restore files.

My code to solve this problem temporarily.

using Renci.SshNet;
using SMBLibrary;
using SMBLibrary.Client;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;


namespace dicom_lib;

public enum ErrorCode : int
{
    Success = 0,
    Unknown = -1,
    ConnectionLost = 400,
    ServerLoginFailure = 401,
    FailedAccessServer = 402,
    FailedAccessShare = 403,
    FailedAccessFile = 404,
    InvalidInstance = 405,
    UninitializedFileStore = 406
}

static class ErrorCodeExtensions
{
    public static int AsInt(this ErrorCode v)
    {
        return (int)v;
    }
}

public struct SmbInstanceNative
{
    public int smbInstanceId;
    public int errorCode;
}

public class SmbInstance
{
    public SMB2Client client;
    public SMB2FileStore fileStore;
    public int id;
}


public class DicomLib
{
    private static int countSmbInstance = -1;

    private static List<SmbInstance> smbInstances = new();

    private static string erroMessage = "";

    private static SmbInstance GetSmb(int id)
    {
        foreach (var item in smbInstances)
        {
            if (item.id == id)
            {
                return item;
            }
        }
        return null;
    }


    [UnmanagedCallersOnly(EntryPoint = "initSmb")]
    public static SmbInstanceNative InitSmb()
    {
        try
        {
            var client = new SMB2Client();
            countSmbInstance++;

            var instanceSmb = new SmbInstance()
            {
                client = client,
                id = countSmbInstance,
            };

            smbInstances.Add(instanceSmb);
            return new SmbInstanceNative() { smbInstanceId = countSmbInstance, errorCode = ErrorCode.Success.AsInt() };

        }
        catch (Exception e)
        {
            erroMessage = $"Unknown error: {e}";
            Console.WriteLine(erroMessage);
            return new SmbInstanceNative()
            {
                smbInstanceId = -1,
                errorCode = ErrorCode.Unknown.AsInt()
            };
        }
    }

    [UnmanagedCallersOnly(EntryPoint = "connectSmb")]
    public static int ConnectSmb(SmbInstanceNative smbInstanceNative, IntPtr pIpSmb,
        IntPtr pDomainSmb,
        IntPtr pUserSmb,
        IntPtr pPassSmb)
    {
        try
        {
            string ipSmb = Marshal.PtrToStringAnsi(pIpSmb);
            string domainSmb = Marshal.PtrToStringAnsi(pDomainSmb);
            string userSmb = Marshal.PtrToStringAnsi(pUserSmb);
            string passSmb = Marshal.PtrToStringAnsi(pPassSmb);

            var instance = GetSmb(smbInstanceNative.smbInstanceId);
            if (instance == null)
            {
                erroMessage = $"invalid instance id";
                Console.WriteLine(erroMessage);
                return ErrorCode.InvalidInstance.AsInt();

            }
            var client = instance.client;            

            var isSmbConnected = client.Connect(System.Net.IPAddress.Parse(ipSmb), SMBTransportType.DirectTCPTransport);
            if (isSmbConnected)
            {
                var status = client.Login(domainSmb, userSmb, passSmb);

                if (status == NTStatus.STATUS_SUCCESS)
                {
                    return ErrorCode.Success.AsInt();
                }
                else
                {
                    erroMessage = $"Failed to login {status}";
                    Console.WriteLine(erroMessage);
                    return ErrorCode.ServerLoginFailure.AsInt();
                }
            }
            else
            {
                erroMessage = "Failed to access the server";
                Console.WriteLine(erroMessage);
                return ErrorCode.FailedAccessServer.AsInt();
            }
        }
        catch (Exception ex)
        {
            erroMessage = $"Unknown error: {ex}";
            Console.WriteLine(erroMessage);
            return ErrorCode.Unknown.AsInt();

        }
    }

    [UnmanagedCallersOnly(EntryPoint = "disconnectSmb")]
    public static int DisconnectSmb(SmbInstanceNative smbInstanceNative)
    {
        try
        {
            var instance = GetSmb(smbInstanceNative.smbInstanceId);
            if (instance == null)
            {
                erroMessage = $"invalid instance id";
                Console.WriteLine(erroMessage);
                return ErrorCode.InvalidInstance.AsInt();

            }
            var client = instance.client;
            client.Disconnect();
            return ErrorCode.Success.AsInt();
        }
        catch (Exception ex)
        {
            erroMessage = $"Unknown error: {ex}";
            Console.WriteLine(erroMessage);
            return ErrorCode.Unknown.AsInt();

        }
    }

    [UnmanagedCallersOnly(EntryPoint = "freeSmb")]
    public static int FreeSmb(SmbInstanceNative smbInstanceNative)
    {
        try
        {
            var instance = GetSmb(smbInstanceNative.smbInstanceId);
            if (instance == null)
            {
                erroMessage = $"invalid instance id";
                Console.WriteLine(erroMessage);
                return ErrorCode.InvalidInstance.AsInt();

            }
            smbInstances.Remove(instance);
            return ErrorCode.Success.AsInt();
        }
        catch (Exception ex)
        {
            erroMessage = $"Unknown error: {ex}";
            Console.WriteLine(erroMessage);
            return ErrorCode.Unknown.AsInt();

        }
    }


    [UnmanagedCallersOnly(EntryPoint = "openShareSmb")]
    public static int OpenShareSmb(SmbInstanceNative smbInstanceNative, IntPtr pShareSmb)
    {
        try
        {
            string shareSmb = Marshal.PtrToStringAnsi(pShareSmb);
            Console.WriteLine($"OpenShareSmb shareSmb {shareSmb}");
            var instance = GetSmb(smbInstanceNative.smbInstanceId);
            if (instance == null)
            {
                erroMessage = $"invalid instance id";
                Console.WriteLine(erroMessage);
                return ErrorCode.InvalidInstance.AsInt();
            }

            var client = instance.client;
            var status = NTStatus.STATUS_SUCCESS;
            var fileStore = client.TreeConnect(shareSmb, out status) as SMB2FileStore;
            if (fileStore != null && status == NTStatus.STATUS_SUCCESS)
            {
                instance.fileStore = fileStore;               
                return ErrorCode.Success.AsInt();
            }

            erroMessage = $"Failed to Access Share {status}";
            Console.WriteLine(erroMessage);
            return ErrorCode.FailedAccessShare.AsInt();
        }
        catch (Exception ex)
        {
            erroMessage = $"Unknown error: {ex}";
            Console.WriteLine(erroMessage);
            return ErrorCode.Unknown.AsInt();

        }
    }


    [UnmanagedCallersOnly(EntryPoint = "getLastError")]
    public static IntPtr GetLastError()
    {
        // Assign pointer of the concatenated string to sumPointer
        IntPtr sumPointer = Marshal.StringToHGlobalAnsi(erroMessage);
        // Return pointer
        return sumPointer;
    }

    [UnmanagedCallersOnly(EntryPoint = "downloadFileSmb")]
    public static int DownloadFileSmb(SmbInstanceNative smbInstanceNative, IntPtr pRemotePathSmb, IntPtr pDestLocalPath)
    {
        try
        {
            var retCode = ErrorCode.Success.AsInt();
            string remotePathSmb = Marshal.PtrToStringAnsi(pRemotePathSmb);
            string destLocalPath = Marshal.PtrToStringAnsi(pDestLocalPath);
            Console.WriteLine($"remotePathSmb: {remotePathSmb}");
            Console.WriteLine($"destLocalPath: {destLocalPath}");

            var instance = GetSmb(smbInstanceNative.smbInstanceId);
            if (instance == null)
            {
                erroMessage = $"invalid instance id";
                Console.WriteLine(erroMessage);
                return ErrorCode.InvalidInstance.AsInt();
            }
            if (instance.fileStore == null)
            {
                erroMessage = $"uninitialized fileStore use OpenShareSmb before";
                Console.WriteLine(erroMessage);
                return ErrorCode.UninitializedFileStore.AsInt();
            }
                  
            object handle;
            FileStatus fileStatus;

            // Open existing file for reading     
            var status = instance.fileStore.CreateFile(out handle, out fileStatus, remotePathSmb,
                 AccessMask.GENERIC_READ, 0, ShareAccess.Read,
                 CreateDisposition.FILE_OPEN,
                 CreateOptions.FILE_NON_DIRECTORY_FILE, null);

            if (status == NTStatus.STATUS_SUCCESS)
            {
                using (var memoryStream = new MemoryStream())
                {
                    byte[] buffer;
                    int bufferLeng = 4096;
                    long offset = 0;
                    while (true)
                    {
                        status = instance.fileStore.ReadFile(out buffer, handle, offset, bufferLeng);

                        if (status == NTStatus.STATUS_END_OF_FILE)
                        {
                            break;
                        }

                        if (status != NTStatus.STATUS_SUCCESS)
                        {
                            retCode = ErrorCode.FailedAccessFile.AsInt();
                            Console.WriteLine("Failed to access file");
                            break;
                        }
                        //(int)offset
                        memoryStream.Write(buffer, 0, buffer.Length);
                        offset += buffer.Length;

                    }
                    instance.fileStore.CloseFile(handle);

                    using (var file = new FileStream(destLocalPath, FileMode.Create, System.IO.FileAccess.Write))
                    {
                        memoryStream.Seek(0, SeekOrigin.Begin);
                        memoryStream.CopyTo(file);
                        memoryStream.Close();
                        file.Close();
                        Console.WriteLine($"end of file copy");
                    }
                }
            }
            else
            {
                Console.WriteLine($"Failed to access file, {status}");
                return ErrorCode.FailedAccessFile.AsInt();                
            }

            return retCode;
        }
        catch (Exception e)
        {
            Console.WriteLine($"Unknown error: {e}");
            return ErrorCode.Unknown.AsInt();
        }
    }
    // callbackDownload (bytesCopied, bytesTotal)
    [UnmanagedCallersOnly(EntryPoint = "copyFileFromSMBtoSFTP")]
    public unsafe static int CopyFileFromSMBtoSFTP(SmbInstanceNative smbInstanceNative, 
        IntPtr pRemotePathSmb, 
        IntPtr pDestSftpPath,
        IntPtr pIpSftpServer,
        int portSftpServer,
        IntPtr pUserSftp,
        IntPtr pPassSftp,
        int pBufferLeng,
        delegate* unmanaged<long, long, int> downloadCallback,
        delegate* unmanaged<long, long, int> uploadCallback
        )
    {
        try
        {
            var retCode = ErrorCode.Success.AsInt();
            string remotePathSmb = Marshal.PtrToStringAnsi(pRemotePathSmb);
            string destSftpPath = Marshal.PtrToStringAnsi(pDestSftpPath);

            string ipSftpServer = Marshal.PtrToStringAnsi(pIpSftpServer);
            string userSftp = Marshal.PtrToStringAnsi(pUserSftp);
            string passSftp = Marshal.PtrToStringAnsi(pPassSftp);

            Console.WriteLine($"CopyFileFromSMBtoSFTP remotePathSmb: {remotePathSmb}");
            Console.WriteLine($"CopyFileFromSMBtoSFTP destSftpPath {destSftpPath}");
            Console.WriteLine($"CopyFileFromSMBtoSFTP ipSftpServer {ipSftpServer}");           
           
            var instance = GetSmb(smbInstanceNative.smbInstanceId);
            if (instance == null)
            {
                erroMessage = $"invalid instance id";
                Console.WriteLine(erroMessage);
                return ErrorCode.InvalidInstance.AsInt();
            }
            if (instance.fileStore == null)
            {
                erroMessage = $"uninitialized fileStore use OpenShareSmb";
                Console.WriteLine(erroMessage);
                return ErrorCode.UninitializedFileStore.AsInt();
            }
                        

            object handle;
            FileStatus fileStatus;

            // Open existing file for reading     
            var status = instance.fileStore.CreateFile(out handle, out fileStatus, remotePathSmb,
                 AccessMask.GENERIC_READ | AccessMask.SYNCHRONIZE,
                 SMBLibrary.FileAttributes.Normal, ShareAccess.Read,
                 CreateDisposition.FILE_OPEN,
                 CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT
                 , null);
            //get File Informations
            FileInformation fileInformationClass;
            long bytesTotal = 0;
            status = instance.fileStore.GetFileInformation(out fileInformationClass, handle, FileInformationClass.FileAllInformation);
            if(status == NTStatus.STATUS_SUCCESS && fileInformationClass != null)
            {
                bytesTotal = fileInformationClass.Length;            
                if (downloadCallback != null)
                {
                    //bytesCopied, bytesTotal
                    downloadCallback(0, bytesTotal);
                }
            }
            if (status == NTStatus.STATUS_SUCCESS)
            {
                using (var memoryStream = new MemoryStream())
                {
                    byte[] buffer;
                    int bufferLeng = pBufferLeng > 1 ? pBufferLeng : (int)instance.client.MaxReadSize;
                    long bytesRead = 0;
                   
                    while (true)
                    {
                        status = instance.fileStore.ReadFile(out buffer, handle, bytesRead, bufferLeng);
                        if (downloadCallback != null)
                        {
                            //bytesCopied, bytesTotal
                            downloadCallback(bytesRead, bytesTotal);
                        }
                                                
                        if (status != NTStatus.STATUS_SUCCESS && status != NTStatus.STATUS_END_OF_FILE)
                        {
                            retCode = ErrorCode.FailedAccessFile.AsInt();
                            Console.WriteLine("Failed to access file");
                            break;
                        }

                        if (status == NTStatus.STATUS_END_OF_FILE || buffer.Length == 0)
                        {
                            break;
                        }
                                            
                        bytesRead  += buffer.Length;
                        memoryStream.Write(buffer, 0, buffer.Length);

                    }
                    instance.fileStore.CloseFile(handle);

                    try
                    {
                        using (SftpClient sftp = new SftpClient(ipSftpServer, portSftpServer, userSftp, passSftp))
                        {
                            sftp.Connect();
                            memoryStream.Seek(0, SeekOrigin.Begin);
                            sftp.UploadFile(memoryStream, destSftpPath, delegate (ulong d)
                            {
                                if(uploadCallback != null)
                                {
                                    uploadCallback((long)d, bytesTotal);
                                }
                            });                           
                            Console.WriteLine($"end of file copy");
                            sftp.Disconnect();
                        }
                    }catch (Exception ex)
                    {
                        Console.WriteLine($"Failed to copy file to SFTP server, {ex}");
                        return ErrorCode.FailedAccessFile.AsInt();
                    }
                    finally
                    {
                        memoryStream.Close();
                    }
                }
            }
            else
            {
                Console.WriteLine($"Failed to access file, {status}");
                return ErrorCode.FailedAccessFile.AsInt();
            }

            return retCode;
        }
        catch (Exception e)
        {
            Console.WriteLine($"Unknown error: {e}");
            return ErrorCode.Unknown.AsInt();
        }
    }

    
}

@jbkempf
Copy link
Contributor

jbkempf commented May 17, 2022

I understand, but Windows has SMB support in, by default. So there should not need a new library for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants