#region COPYRIGHT (c) 2007 by Matthias Fischer // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR // PURPOSE. // // This material may not be duplicated in whole or in part, except for // personal use, without the express written consent of the author. // // Autor: Matthais Fischer // Email: mfischer@comzept.de // // Copyright (C) 2007 Matthias Fischer. All Rights Reserved. #endregion using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; namespace Comzept.Genesis.NetworkTools { /// /// Implementation of Basic TFTP Client Functions /// public class TFTPClient { #region -=[ Declarations ]=- /// /// TFTP opcodes /// public enum Opcodes { Unknown = 0, Read = 1, Write = 2, Data = 3, Ack = 4, Error = 5 } /// /// TFTP modes /// public enum Modes { Unknown = 0, NetAscii = 1, Octet = 2, Mail = 3 } /// /// 报告传输完成进度事件委托 /// /// 已完成传输块数 /// 总块数 public delegate void ReportCompletedProgressEventDelegate(string remoteIP, int completedBlock, int totalBlock); /// /// A TFTP Exception /// public class TFTPException : Exception { public string ErrorMessage = ""; public int ErrorCode = -1; /// /// Initializes a new instance of the class. /// /// The err code. /// The err MSG. public TFTPException(int errCode, string errMsg) { ErrorCode = errCode; ErrorMessage = errMsg; } /// /// Creates and returns a string representation of the current exception. /// /// /// A string representation of the current exception. /// /// 1 /// /// /// public override string ToString() { return String.Format("TFTPException: ErrorCode: {0} Message: {1}", ErrorCode, ErrorMessage); } } private int tftpPort; private string tftpServer = ""; private int blockSize = 512; #endregion #region -=[ Ctor ]=- /// /// Initializes a new instance of the class. /// /// The server. public TFTPClient(string server) : this(server, 69) { } /// /// Initializes a new instance of the class. /// /// The server. /// The port. public TFTPClient(string server, int port) { Server = server; Port = port; } #endregion #region -=[ Public Properties ]=- /// /// Gets the port. /// /// The port. public int Port { get { return tftpPort; } private set { tftpPort = value; } } /// /// Gets the server. /// /// The server. public string Server { get { return tftpServer; } private set { tftpServer = value; } } /// /// 块大小 /// public int BlockSize { get { return this.blockSize; } private set { this.blockSize = value; } } #endregion #region -=[ Events ]=- public event ReportCompletedProgressEventDelegate ReportCompletedProgress; #endregion #region -=[ Public Member ]=- /// /// Gets the specified remote file. /// /// The remote file. /// The local file. public void Get(string remoteFile, string localFile) { Get(remoteFile, localFile, Modes.Octet); } /// /// Gets the specified remote file. /// /// The remote file. /// The local file. /// The TFTP mode. public void Get(string remoteFile, string localFile, Modes tftpMode) { int len = 0; int packetNr = 1; byte[] sndBuffer = CreateRequestPacket(Opcodes.Read, remoteFile, tftpMode); byte[] rcvBuffer = new byte[516]; BinaryWriter fileStream = new BinaryWriter(new FileStream(localFile, FileMode.Create, FileAccess.Write, FileShare.Read)); //IPHostEntry hostEntry = Dns.GetHostEntry(tftpServer); //IPEndPoint serverEP = new IPEndPoint(hostEntry.AddressList[0], tftpPort); IPEndPoint serverEP = new IPEndPoint(IPAddress.Parse(tftpServer), tftpPort); EndPoint dataEP = (EndPoint)serverEP; Socket tftpSocket = new Socket(serverEP.Address.AddressFamily, SocketType.Dgram, ProtocolType.Udp); // Request and Receive first Data Packet From TFTP Server tftpSocket.SendTo(sndBuffer, sndBuffer.Length, SocketFlags.None, serverEP); tftpSocket.ReceiveTimeout = 10000; len = tftpSocket.ReceiveFrom(rcvBuffer, ref dataEP); // keep track of the TID serverEP.Port = ((IPEndPoint)dataEP).Port; while (true) { // handle any kind of error if (((Opcodes)rcvBuffer[1]) == Opcodes.Error) { fileStream.Close(); tftpSocket.Close(); throw new TFTPException(((rcvBuffer[2] << 8) & 0xff00) | rcvBuffer[3], Encoding.ASCII.GetString(rcvBuffer, 4, rcvBuffer.Length - 5).Trim('\0')); } // expect the next packet if ((((rcvBuffer[2] << 8) & 0xff00) | rcvBuffer[3]) == packetNr) { // Store to local file fileStream.Write(rcvBuffer, 4, len - 4); // Send Ack Packet to TFTP Server sndBuffer = CreateAckPacket(packetNr++); tftpSocket.SendTo(sndBuffer, sndBuffer.Length, SocketFlags.None, serverEP); } // Was ist the last packet ? if (len < 516) { break; } else { // Receive Next Data Packet From TFTP Server len = tftpSocket.ReceiveFrom(rcvBuffer, ref dataEP); } } // Close Socket and release resources tftpSocket.Close(); fileStream.Close(); } /// /// Puts the specified remote file. /// /// The remote file. /// The local file. public void Put(string remoteFile, string localFile) { Put(remoteFile, localFile, Modes.Octet); } /// /// Puts the specified remote file. /// /// The remote file. /// The local file. /// The TFTP mode. /// What if the ack does not come ! public void Put(string remoteFile, string localFile, Modes tftpMode) { int len = 0; byte[] sndBuffer = CreateRequestPacket(Opcodes.Write, remoteFile, tftpMode); byte[] rcvBuffer = new byte[516]; BinaryReader fileStreamReader = new BinaryReader(new FileStream(localFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); //IPHostEntry hostEntry = Dns.GetHostEntry(tftpServer); //IPEndPoint serverEP = new IPEndPoint(hostEntry.AddressList[0], tftpPort); IPEndPoint serverEP = new IPEndPoint(IPAddress.Parse(tftpServer), tftpPort); EndPoint dataEP = (EndPoint)serverEP; Socket tftpSocket = new Socket(serverEP.Address.AddressFamily, SocketType.Dgram, ProtocolType.Udp); // Request Writing to TFTP Server tftpSocket.SendTo(sndBuffer, sndBuffer.Length, SocketFlags.None, serverEP); len = tftpSocket.ReceiveFrom(rcvBuffer, ref dataEP); tftpSocket.ReceiveTimeout = 5000; // keep track of the TID serverEP.Port = ((IPEndPoint)dataEP).Port; int tryCount = 5; int packetTl = (int)Math.Ceiling((double)fileStreamReader.BaseStream.Length / this.blockSize); for (int packetNr = 1; packetNr <= packetTl; packetNr++) { // handle any kind of error if (((Opcodes)rcvBuffer[1]) == Opcodes.Error) { fileStreamReader.Close(); tftpSocket.Close(); throw new TFTPException(((rcvBuffer[2] << 8) & 0xff00) | rcvBuffer[3], Encoding.ASCII.GetString(rcvBuffer, 4, rcvBuffer.Length - 5).Trim('\0')); } // expect the next packet ack if ((((Opcodes)rcvBuffer[1]) == Opcodes.Ack) && (((rcvBuffer[2] << 8) & 0xff00) | rcvBuffer[3]) == (packetNr - 1)) { fileStreamReader.BaseStream.Seek((packetNr - 1) * blockSize, SeekOrigin.Begin); sndBuffer = CreateDataPacket(packetNr, fileStreamReader.ReadBytes(512)); tftpSocket.SendTo(sndBuffer, sndBuffer.Length, SocketFlags.None, serverEP); } if (ReportCompletedProgress != null) { ReportCompletedProgress.Invoke(serverEP.Address.ToString(), packetNr, packetTl); } try { len = tftpSocket.ReceiveFrom(rcvBuffer, ref dataEP); tryCount = 5; } catch (SocketException ex) { if (ex.SocketErrorCode == SocketError.TimedOut && tryCount >= 0) { tryCount--; packetNr = (packetNr <= 0 ? 0 : --packetNr); } else { throw ex; } } } // Close Socket and release resources tftpSocket.Close(); fileStreamReader.Close(); } #endregion #region -=[ Private Member ]=- /// /// Creates the request packet. /// /// The op code. /// The remote file. /// The TFTP mode. /// the ack packet private byte[] CreateRequestPacket(Opcodes opCode, string remoteFile, Modes tftpMode) { // Create new Byte array to hold Initial // Read Request Packet int pos = 0; string modeAscii = tftpMode.ToString().ToLowerInvariant(); byte[] ret = new byte[modeAscii.Length + remoteFile.Length + 4]; // Set first Opcode of packet to indicate // if this is a read request or write request ret[pos++] = 0; ret[pos++] = (byte)opCode; // Convert Filename to a char array pos += Encoding.ASCII.GetBytes(remoteFile, 0, remoteFile.Length, ret, pos); ret[pos++] = 0; pos += Encoding.ASCII.GetBytes(modeAscii, 0, modeAscii.Length, ret, pos); ret[pos] = 0; return ret; } /// /// Creates the data packet. /// /// The packet nr. /// The data. /// the data packet private byte[] CreateDataPacket(int blockNr, byte[] data) { // Create Byte array to hold ack packet byte[] ret = new byte[4 + data.Length]; // Set first Opcode of packet to TFTP_ACK ret[0] = 0; ret[1] = (byte)Opcodes.Data; ret[2] = (byte)((blockNr >> 8) & 0xff); ret[3] = (byte)(blockNr & 0xff); Array.Copy(data, 0, ret, 4, data.Length); return ret; } /// /// Creates the ack packet. /// /// The block nr. /// the ack packet private byte[] CreateAckPacket(int blockNr) { // Create Byte array to hold ack packet byte[] ret = new byte[4]; // Set first Opcode of packet to TFTP_ACK ret[0] = 0; ret[1] = (byte)Opcodes.Ack; // Insert block number into packet array ret[2] = (byte)((blockNr >> 8) & 0xff); ret[3] = (byte)(blockNr & 0xff); return ret; } #endregion } }