//****************************************************************************************************** // TFtpServer.cs - Gbtc // // Copyright © 2020, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this // file except in compliance with the License. You may obtain a copy of the License at: // // http://opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 05/12/2020 - J. Ritchie Carroll // Generated original version of source code. // //****************************************************************************************************** #region [ Contributor License Agreements ] //******************************************************************************************************* // // Code based on the following project: // https://github.com/Callisto82/tftp.net // // Copyright © 2011, Michael Baer // //******************************************************************************************************* #endregion using System; using System.Net; using GSF.Net.TFtp.Channel; using GSF.Net.TFtp.Commands; using GSF.Net.TFtp.Transfer; // ReSharper disable InconsistentNaming namespace GSF.Net.TFtp { public delegate void TFtpServerEventHandler(ITFtpTransfer transfer, EndPoint client); public delegate void TFtpServerErrorHandler(TFtpTransferError error); /// /// A simple TFTP server class. Dispose() the server to close the socket that it listens on. /// public class TFtpServer : IDisposable { public const int DefaultServerPort = 69; /// /// Fired when the server receives a new read request. /// public event TFtpServerEventHandler OnReadRequest; /// /// Fired when the server receives a new write request. /// public event TFtpServerEventHandler OnWriteRequest; /// /// Fired when the server encounters an error (for example, a non-parseable request) /// public event TFtpServerErrorHandler OnError; private readonly ITransferChannel m_serverSocket; // Server port that we're listening on. private bool m_disposed; public TFtpServer(IPEndPoint localAddress) { if (localAddress == null) throw new ArgumentNullException(nameof(localAddress)); m_serverSocket = TransferChannelFactory.CreateServer(localAddress); m_serverSocket.OnCommandReceived += ServerSocket_OnCommandReceived; m_serverSocket.OnError += ServerSocket_OnError; } public TFtpServer(IPAddress localAddress) : this(localAddress, DefaultServerPort) { } public TFtpServer(IPAddress localAddress, int port) : this(new IPEndPoint(localAddress, port)) { } public TFtpServer(int port) : this(new IPEndPoint(IPAddress.Any, port)) { } public TFtpServer() : this(DefaultServerPort) { } /// /// Releases all the resources used by the object. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (m_disposed) return; try { if (disposing) { m_serverSocket?.Dispose(); } } finally { m_disposed = true; // Prevent duplicate dispose. } } /// /// Start accepting incoming connections. /// public void Start() { m_serverSocket.Open(); } private void ServerSocket_OnError(TFtpTransferError error) { RaiseOnError(error); } private void ServerSocket_OnCommandReceived(ITFtpCommand command, EndPoint endpoint) { // Ignore all other commands if (!(command is ReadOrWriteRequest)) return; // Open a connection to the client ITransferChannel channel = TransferChannelFactory.CreateConnection(endpoint); // Create a wrapper for the transfer request ReadOrWriteRequest request = (ReadOrWriteRequest)command; ITFtpTransfer transfer = request is ReadRequest ? (ITFtpTransfer)new LocalReadTransfer(channel, request.Filename, request.Options) : new LocalWriteTransfer(channel, request.Filename, request.Options); switch (command) { case ReadRequest _: RaiseOnReadRequest(transfer, endpoint); break; case WriteRequest _: RaiseOnWriteRequest(transfer, endpoint); break; default: throw new Exception($"Unexpected TFTP transfer request: {command}"); } } private void RaiseOnError(TFtpTransferError error) { OnError?.Invoke(error); } private void RaiseOnWriteRequest(ITFtpTransfer transfer, EndPoint client) { if (OnWriteRequest != null) OnWriteRequest(transfer, client); else transfer.Cancel(new TFtpErrorPacket(0, "Server did not provide a OnWriteRequest handler.")); } private void RaiseOnReadRequest(ITFtpTransfer transfer, EndPoint client) { if (OnReadRequest != null) OnReadRequest(transfer, client); else transfer.Cancel(new TFtpErrorPacket(0, "Server did not provide a OnReadRequest handler.")); } } }