diff --git a/flake.nix b/flake.nix index 542c239..b099c89 100644 --- a/flake.nix +++ b/flake.nix @@ -22,6 +22,7 @@ sqlite sqls sql-studio + zlib # Backend (dotnetCorePackages.combinePackages [ dotnetCorePackages.sdk_9_0 @@ -37,7 +38,8 @@ typescript-language-server ]; shellHook = '' - export PATH=$PATH:$HOME/.bun/bin + export PATH=$PATH: + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${pkgs.zlib}/lib ''; }; diff --git a/server.test/CommonTest.cs b/server.test/CommonTest.cs index 782a428..1f36b90 100644 --- a/server.test/CommonTest.cs +++ b/server.test/CommonTest.cs @@ -1,3 +1,4 @@ +using System.Buffers.Binary; using Common; namespace server.test; @@ -40,9 +41,21 @@ public class CommonTest [Fact] public void ToBitTest() { - Assert.Equal(Number.ToBit(0xFF, 3).Value, true); - Assert.Equal(Number.ToBit(0x00, 3).Value, false); - Assert.Equal(Number.ToBit(0xAA, 3).Value, true); - Assert.Equal(Number.ToBit(0xAA, 2).Value, false); + Assert.Equal(true, Number.ToBit(0xFF, 3).Value); + Assert.Equal(false, Number.ToBit(0x00, 3).Value); + Assert.Equal(true, Number.ToBit(0xAA, 3).Value); + Assert.Equal(false, Number.ToBit(0xAA, 2).Value); + } + + [Fact] + public void ReverseBits() + { + Assert.Equal((byte)0x05, Common.Number.ReverseBits((byte)0xA0)); + + var bytesSrc = new byte[] { 0xAB, 0x00, 0x00, 0x01 }; + var bytes = new byte[4]; + for (int i = 0; i < 4; i++) + bytes[i] = Common.Number.ReverseBits(bytesSrc[i]); + Assert.Equal(new byte[] { 0xD5, 0x00, 0x00, 0x80 }, bytes); } } diff --git a/server.test/UDPServerTest.cs b/server.test/UDPServerTest.cs index 5cde6f3..3274507 100644 --- a/server.test/UDPServerTest.cs +++ b/server.test/UDPServerTest.cs @@ -92,7 +92,7 @@ public class UDPServerTest Assert.True(ret.HasValue); var data = ret.Value; Assert.Equal(address, data.Address); - Assert.Equal(number, Number.BytesToNumber(data.Data)); + Assert.Equal(number, Number.BytesToUInt32(data.Data).Value); } } @@ -112,7 +112,7 @@ public class UDPServerTest Assert.True(ret.IsSuccessful); var data = ret.Value; Assert.True(data.IsSuccessful); - Assert.Equal(number, Number.BytesToNumber(data.ToBytes())); + Assert.Equal(number, Number.BytesToUInt32(data.ToBytes()).Value); } } @@ -132,7 +132,7 @@ public class UDPServerTest Assert.True(ret.IsSuccessful); var data = ret.Value; Assert.True(data.IsSuccessful); - Assert.Equal(number, Number.BytesToNumber(data.ToBytes())); + Assert.Equal((UInt64)number, Number.BytesToUInt64(data.ToBytes()).Value); } } } diff --git a/server/Program.cs b/server/Program.cs index 02fd163..0b3c10c 100644 --- a/server/Program.cs +++ b/server/Program.cs @@ -37,16 +37,17 @@ try }); // Add CORS policy - builder.Services.AddCors(options => + if (builder.Environment.IsDevelopment()) { - options.AddPolicy("Development", policy => + builder.Services.AddCors(options => { - policy + options.AddPolicy("Development", policy => policy .AllowAnyOrigin() .AllowAnyMethod() - .AllowAnyHeader(); + .AllowAnyHeader() + ); }); - }); + } // Add Swagger builder.Services.AddControllers(); diff --git a/server/server.csproj b/server/server.csproj index dca031e..a6dc9b5 100644 --- a/server/server.csproj +++ b/server/server.csproj @@ -13,12 +13,15 @@ + + + diff --git a/server/src/Common.cs b/server/src/Common.cs index 194b409..5639ef7 100644 --- a/server/src/Common.cs +++ b/server/src/Common.cs @@ -7,14 +7,48 @@ namespace Common /// public class Number { + private static readonly byte[] BitReverseTable = new byte[] { + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, + 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, + 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, + 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, + 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, + 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, + 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, + 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, + 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, + 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, + 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, + 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, + 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, + 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, + 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, + 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, + 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff + }; /// /// 整数转成二进制字节数组 /// /// 整数 /// 整数长度 - /// 是否高位在右边 + /// 是否高位在数组索引低 /// 二进制字节数组 - public static Result NumberToBytes(ulong num, uint length, bool isRightHigh = false) + public static Result NumberToBytes(ulong num, uint length, bool isLowNumHigh = false) { if (length > 8) { @@ -26,7 +60,7 @@ namespace Common var arr = new byte[length]; - if (isRightHigh) + if (isLowNumHigh) { for (var i = 0; i < length; i++) { @@ -44,12 +78,12 @@ namespace Common } /// - /// 二进制字节数组转成整数 + /// 二进制字节数组转成64bits整数 /// /// 二进制字节数组 - /// 是否高位在右边 + /// 是否高位在数组索引低 /// 整数 - public static Result BytesToNumber(byte[] bytes, bool isRightHigh = false) + public static Result BytesToUInt64(byte[] bytes, bool isLowNumHigh = false) { if (bytes.Length > 8) { @@ -59,27 +93,78 @@ namespace Common )); } - ulong num = 0; + UInt64 num = 0; int len = bytes.Length; - if (isRightHigh) + try { - for (var i = 0; i < len; i++) + if (isLowNumHigh) { - num += Convert.ToUInt64((UInt64)bytes[len - 1 - i] << (i << 3)); + for (var i = 0; i < len; i++) + { + num += Convert.ToUInt64((UInt64)bytes[len - 1 - i] << (i << 3)); + } } - } - else - { - for (var i = 0; i < len; i++) + else { - num += Convert.ToUInt64((UInt64)bytes[i] << ((int)(len - 1 - i) << 3)); + for (var i = 0; i < len; i++) + { + num += Convert.ToUInt64((UInt64)bytes[i] << ((int)(len - 1 - i) << 3)); + } } - } - return num; + return num; + } + catch (Exception error) + { + return new(error); + } } + /// + /// 二进制字节数组转成32bits整数 + /// + /// 二进制字节数组 + /// 是否高位在数组索引低 + /// 整数 + public static Result BytesToUInt32(byte[] bytes, bool isLowNumHigh = false) + { + if (bytes.Length > 4) + { + return new(new ArgumentException( + "Unsigned long number can't over 4 bytes(32 bits).", + nameof(bytes) + )); + } + + UInt32 num = 0; + int len = bytes.Length; + + try + { + if (isLowNumHigh) + { + for (var i = 0; i < len; i++) + { + num += Convert.ToUInt32((UInt32)bytes[len - 1 - i] << (i << 3)); + } + } + else + { + for (var i = 0; i < len; i++) + { + num += Convert.ToUInt32((UInt32)bytes[i] << ((int)(len - 1 - i) << 3)); + } + } + + return num; + } + catch (Exception error) + { + return new(error); + } + + } /// @@ -152,6 +237,12 @@ namespace Common return (srcBits & mask) == dstBits; } + /// + /// 获取整型对应位置的比特 + /// + /// 整型数字 + /// 位置 + /// 比特 public static Result ToBit(UInt32 srcBits, int location) { if (location < 0) @@ -199,7 +290,7 @@ namespace Common "Distance is larger than bytesArray", nameof(distance))); if (srcBytesLen % distance != 0) return new(new ArgumentException( - "The length of bytes can't be divided by 2 without reminder", nameof(distance))); + "The length of bytes can't be divided by distance without reminder", nameof(distance))); var dstBytes = new byte[srcBytesLen]; var buffer = new byte[distance]; @@ -215,7 +306,31 @@ namespace Common return dstBytes; } + /// + /// 反转字节内比特顺序(使用查找表的方法) + /// + /// 字节 + /// 反转后的字节 + public static byte ReverseBits(byte srcByte) + { + return BitReverseTable[srcByte]; + } + /// + /// 反转字节数组的字节内比特顺序(使用查找表的方法) + /// + /// 字节数组 + /// 反转后的字节字节数组 + public static byte[] ReverseBits(byte[] srcBytes) + { + var bytesLen = srcBytes.Length; + var dstBytes = new byte[bytesLen]; + for (int i = 0; i < bytesLen; i++) + { + dstBytes[i] = BitReverseTable[srcBytes[i]]; + } + return dstBytes; + } } /// diff --git a/server/src/Controllers.cs b/server/src/Controllers.cs index 42405b2..968ab0a 100644 --- a/server/src/Controllers.cs +++ b/server/src/Controllers.cs @@ -1,6 +1,7 @@ -using System.Buffers.Binary; using System.Net; using Common; +using DotNext; +using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using WebProtocol; @@ -142,6 +143,8 @@ public class JtagController : ControllerBase { private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + private const string BITSTREAM_PATH = "bitstream/Jtag"; + /// /// 页面 /// @@ -243,7 +246,7 @@ public class JtagController : ControllerBase // 生成安全的文件名(避免路径遍历攻击) var fileName = Path.GetRandomFileName(); - var uploadsFolder = Path.Combine(Environment.CurrentDirectory, $"bitstream/{address}"); + var uploadsFolder = Path.Combine(Environment.CurrentDirectory, $"{BITSTREAM_PATH}/{address}"); // 如果存在文件,则删除原文件再上传 if (Directory.Exists(uploadsFolder)) @@ -275,7 +278,7 @@ public class JtagController : ControllerBase public async ValueTask DownloadBitstream(string address, int port) { // 检查文件 - var fileDir = Path.Combine(Environment.CurrentDirectory, $"bitstream/{address}"); + var fileDir = Path.Combine(Environment.CurrentDirectory, $"{BITSTREAM_PATH}/{address}"); if (!Directory.Exists(fileDir)) return TypedResults.BadRequest("Empty bitstream, Please upload it first"); @@ -305,10 +308,6 @@ public class JtagController : ControllerBase if (!retBuffer.IsSuccessful) return TypedResults.InternalServerError(retBuffer.Error); revBuffer = retBuffer.Value; - for (int i = 0; i < buffer.Length; i++) - { - revBuffer[i] = BinaryPrimitives.ReverseEndianness(revBuffer[i]); - } await memoryStream.WriteAsync(revBuffer, 0, bytesRead); totalBytesRead += bytesRead; @@ -347,6 +346,325 @@ public class JtagController : ControllerBase } } +/// +/// 远程更新 +/// +[ApiController] +[Route("api/[controller]")] +public class RemoteUpdater : ControllerBase +{ + private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + + private const string BITSTREAM_PATH = "bitstream/RemoteUpdate"; + + /// + /// 上传远程更新比特流文件 + /// + /// 设备地址 + /// 黄金比特流文件 + /// 比特流文件1 + /// 比特流文件2 + /// 比特流文件3 + /// 上传结果 + [HttpPost("UploadBitstream")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async ValueTask UploadBitstreams( + string address, + IFormFile? goldenBitream, + IFormFile? bitstream1, + IFormFile? bitstream2, + IFormFile? bitstream3) + { + if ((goldenBitream is null || goldenBitream.Length == 0) && + (bitstream1 is null || bitstream1.Length == 0) && + (bitstream2 is null || bitstream2.Length == 0) && + (bitstream3 is null || bitstream3.Length == 0)) + return TypedResults.BadRequest("未选择文件"); + + // 生成安全的文件名(避免路径遍历攻击) + var fileName = Path.GetRandomFileName(); + var uploadsFolder = Path.Combine(Environment.CurrentDirectory, $"{BITSTREAM_PATH}/{address}"); + + // 如果存在文件,则删除原文件再上传 + if (Directory.Exists(uploadsFolder)) + { + Directory.Delete(uploadsFolder, true); + } + Directory.CreateDirectory(uploadsFolder); + + for (int bitstreamNum = 0; bitstreamNum < 4; bitstreamNum++) + { + IFormFile file; + if (bitstreamNum == 0 && goldenBitream is not null) + file = goldenBitream; + else if (bitstreamNum == 1 && bitstream1 is not null) + file = bitstream1; + else if (bitstreamNum == 2 && bitstream2 is not null) + file = bitstream2; + else if (bitstreamNum == 3 && bitstream3 is not null) + file = bitstream3; + else continue; + + var fileFolder = Path.Combine(uploadsFolder, bitstreamNum.ToString()); + Directory.CreateDirectory(fileFolder); + + var filePath = Path.Combine(fileFolder, fileName); + + using (var stream = new FileStream(filePath, FileMode.Create)) + { + await file.CopyToAsync(stream); + } + } + + logger.Info($"Device {address} Upload Bitstream Successfully"); + return TypedResults.Ok("Bitstream Upload Successfully"); + } + + private async ValueTask> ProcessBitstream(string filePath) + { + using (var fileStream = System.IO.File.Open(filePath, System.IO.FileMode.Open)) + { + if (fileStream is null || fileStream.Length <= 0) + return new(new ArgumentException("Wrong bitstream path")); + + // 定义缓冲区大小: 32KB + byte[] buffer = new byte[32 * 1024]; + byte[] revBuffer = new byte[32 * 1024]; + long totalBytesRead = 0; + + // 使用异步流读取文件 + using (var memoryStream = new MemoryStream()) + { + int bytesRead; + while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + // 反转 32bits + var retBuffer = Common.Number.ReverseBytes(buffer, 4); + if (!retBuffer.IsSuccessful) + return new(retBuffer.Error); + revBuffer = retBuffer.Value; + + await memoryStream.WriteAsync(revBuffer, 0, bytesRead); + totalBytesRead += bytesRead; + } + + // 将所有数据转换为字节数组(注意:如果文件非常大,可能不适合完全加载到内存) + var restStreamLen = memoryStream.Length % (4 * 1024); + if (restStreamLen != 0) + { + var appendLen = ((int)(4 * 1024 - restStreamLen)); + var bytesAppend = new byte[appendLen]; + Array.Fill(bytesAppend, 0xFF); + await memoryStream.WriteAsync(bytesAppend, 0, appendLen); + } + + return new(memoryStream.ToArray()); + } + } + } + + /// + /// 远程更新单个比特流文件 + /// + /// 设备地址 + /// 设备端口 + /// 比特流位号 + [HttpPost("DownloadBitstream")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async ValueTask UpdateBitstream(string address, int port, int bitstreamNum) + { + // 检查文件 + var fileDir = Path.Combine(Environment.CurrentDirectory, $"{BITSTREAM_PATH}/{address}/{bitstreamNum}"); + if (!Directory.Exists(fileDir)) + return TypedResults.BadRequest("Empty bitstream, Please upload it first"); + + try + { + // 读取文件 + var filePath = Directory.GetFiles(fileDir)[0]; + + var fileBytes = await ProcessBitstream(filePath); + if (!fileBytes.IsSuccessful) return TypedResults.InternalServerError(fileBytes.Error); + + // 下载比特流 + var remoteUpdater = new RemoteUpdate.RemoteUpdateClient(address, port); + var ret = await remoteUpdater.UpdateBitstream(bitstreamNum, fileBytes.Value); + + if (ret.IsSuccessful) + { + logger.Info($"Device {address} Update bitstream successfully"); + return TypedResults.Ok(ret.Value); + } + else + { + logger.Error(ret.Error); + return TypedResults.InternalServerError(ret.Error); + } + + } + catch (Exception error) + { + return TypedResults.InternalServerError(error); + } + } + + + /// + /// 下载多个比特流文件 + /// + /// 设备地址 + /// 设备端口 + /// 比特流编号 + /// 总共上传比特流的数量 + [HttpPost("DownloadMultiBitstreams")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async ValueTask DownloadMultiBitstreams(string address, int port, int? bitstreamNum) + { + // 检查文件 + var bitstreamsFolder = Path.Combine(Environment.CurrentDirectory, $"{BITSTREAM_PATH}/{address}"); + if (!Directory.Exists(bitstreamsFolder)) + return TypedResults.BadRequest("Empty bitstream, Please upload it first"); + + try + { + var bitstreams = new List() { null, null, null, null }; + int cnt = 0; // 上传比特流数量 + for (int i = 0; i < 4; i++) + { + var bitstreamDir = Path.Combine(bitstreamsFolder, i.ToString()); + if (!Directory.Exists(bitstreamDir)) + continue; + cnt++; + + // 读取文件 + var filePath = Directory.GetFiles(bitstreamDir)[0]; + var fileBytes = await ProcessBitstream(filePath); + if (!fileBytes.IsSuccessful) return TypedResults.InternalServerError(fileBytes.Error); + bitstreams[i] = fileBytes.Value; + } + + // 下载比特流 + var remoteUpdater = new RemoteUpdate.RemoteUpdateClient(address, port); + { + var ret = await remoteUpdater.UploadBitstreams(bitstreams[0], bitstreams[1], bitstreams[2], bitstreams[3]); + if (!ret.IsSuccessful) return TypedResults.InternalServerError(ret.Error); + if (!ret.Value) return TypedResults.InternalServerError("Upload MultiBitstreams failed"); + } + + if (bitstreamNum is not null) + { + var ret = await remoteUpdater.HotResetBitstream(bitstreamNum ?? 0); + if (!ret.IsSuccessful) return TypedResults.InternalServerError(ret.Error); + if (!ret.Value) return TypedResults.InternalServerError("Hot reset failed"); + } + return TypedResults.Ok(cnt); + } + catch (Exception error) + { + return TypedResults.InternalServerError(error); + } + } + + + /// + /// 热复位比特流文件 + /// + /// 设备地址 + /// 设备端口 + /// 比特流编号 + /// 操作结果 + [HttpPost("HotResetBitstream")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async ValueTask HotResetBitstream(string address, int port, int bitstreamNum) + { + var remoteUpdater = new RemoteUpdate.RemoteUpdateClient(address, port); + var ret = await remoteUpdater.HotResetBitstream(bitstreamNum); + + if (ret.IsSuccessful) + { + logger.Info($"Device {address}即可 Update bitstream successfully"); + return TypedResults.Ok(ret.Value); + } + else + { + logger.Error(ret.Error); + return TypedResults.InternalServerError(ret.Error); + } + } +} + + +/// +/// 数据控制器 +/// +[ApiController] +[Route("api/[controller]")] +public class Data : ControllerBase +{ + private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + + /// + /// 创建数据库表 + /// + /// 插入的记录数 + [EnableCors("Development")] + [HttpPost("CreateTable")] + public IResult CreateTables() + { + using var db = new Database.AppDataConnection(); + db.CreateAllTables(); + return TypedResults.Ok(); + } + + /// + /// 删除数据库表 + /// + /// 插入的记录数 + [EnableCors("Development")] + [HttpDelete("DropTables")] + public IResult DropTables() + { + using var db = new Database.AppDataConnection(); + db.DropAllTables(); + return TypedResults.Ok(); + } + + /// + /// 获取所有用户 + /// + /// 用户列表 + [HttpGet("AllUsers")] + public IResult AllUsers() + { + using var db = new Database.AppDataConnection(); + var ret = db.User.ToList(); + return TypedResults.Ok(ret); + } + + /// + /// 注册新用户 + /// + /// 用户名 + /// 操作结果 + [HttpPost("SignUpUser")] + public IResult SignUpUser(string name) + { + if (name.Length > 255) + return TypedResults.BadRequest("Name Couln't over 255 characters"); + + using var db = new Database.AppDataConnection(); + var ret = db.AddUser(name); + return TypedResults.Ok(ret); + } +} + /// /// 日志控制器 /// diff --git a/server/src/Database.cs b/server/src/Database.cs new file mode 100644 index 0000000..e54d831 --- /dev/null +++ b/server/src/Database.cs @@ -0,0 +1,113 @@ +using LinqToDB; +using LinqToDB.Data; +using LinqToDB.Mapping; + +namespace Database; + +/// +/// 用户类,表示用户信息 +/// +public class User +{ + /// + /// 用户的唯一标识符 + /// + [PrimaryKey] + public Guid ID { get; set; } = Guid.NewGuid(); + + /// + /// 用户的名称 + /// + [NotNull] + public required string Name { get; set; } +} + +/// +/// FPGA 板子类,表示板子信息 +/// +public class Board +{ + /// + /// FPGA 板子的唯一标识符 + /// + [PrimaryKey] + public Guid Id { get; set; } = Guid.NewGuid(); + + /// + /// FPGA 板子的名称 + /// + [NotNull] + public required string BoardName { get; set; } +} + +/// +/// 应用程序数据连接类,用于与数据库交互 +/// +public class AppDataConnection : DataConnection +{ + static readonly LinqToDB.DataOptions options = + new LinqToDB.DataOptions() + .UseSQLite($"Data Source={Environment.CurrentDirectory}/Database.sqlite"); + + /// + /// 初始化应用程序数据连接 + /// + public AppDataConnection() : base(options) { } + + + /// + /// 创建所有数据库表 + /// + public void CreateAllTables() + { + this.CreateTable(); + this.CreateTable(); + } + + /// + /// 删除所有数据库表 + /// + public void DropAllTables() + { + this.DropTable(); + this.DropTable(); + } + + /// + /// 添加一个新的用户到数据库 + /// + /// 用户的名称 + /// 插入的记录数 + public int AddUser(string name) + { + var user = new User() + { + Name = name + }; + return this.Insert(user); + } + + /// + /// 添加一块新的 FPGA 板子到数据库 + /// + /// FPGA 板子的名称 + /// 插入的记录数 + public int AddBoard(string name) + { + var board = new Board() + { + BoardName = name + }; + return this.Insert(board); + } + + /// + /// 用户表 + /// + public ITable User => this.GetTable(); + + /// + /// FPGA 板子表 + /// + public ITable Board => this.GetTable(); +} diff --git a/server/src/JtagClient.cs b/server/src/JtagClient.cs index cca5b27..b02ed6e 100644 --- a/server/src/JtagClient.cs +++ b/server/src/JtagClient.cs @@ -428,7 +428,7 @@ public class Jtag if (retPackLen != 4) return new(new Exception($"RecvDataPackage BodyData Length not Equal to 4: Total {retPackLen} bytes")); - return Convert.ToUInt32(Common.Number.BytesToNumber(retPackOpts.Data).Value); + return Convert.ToUInt32(Common.Number.BytesToUInt64(retPackOpts.Data).Value); } /// @@ -558,7 +558,7 @@ public class Jtag return new(new Exception($"RecvDataPackage BodyData Length not Equal to 4: Total {retPackLen} bytes")); if (Common.Number.BitsCheck( - Common.Number.BytesToNumber(retPack.Value.Options.Data).Value, result, resultMask)) + Common.Number.BytesToUInt64(retPack.Value.Options.Data).Value, result, resultMask)) ret = true; return ret; @@ -578,7 +578,7 @@ public class Jtag return new(new Exception($"RecvDataPackage BodyData Length not Equal to 4: Total {retPackLen} bytes")); if (Common.Number.BitsCheck( - Common.Number.BytesToNumber(retPack.Value.Options.Data).Value, result, resultMask)) + Common.Number.BytesToUInt64(retPack.Value.Options.Data).Value, result, resultMask)) ret = true; return ret; diff --git a/server/src/RemoteUpdate.cs b/server/src/RemoteUpdate.cs new file mode 100644 index 0000000..b6db774 --- /dev/null +++ b/server/src/RemoteUpdate.cs @@ -0,0 +1,535 @@ +using System.Net; +using DotNext; +namespace RemoteUpdate; + +static class RemoteUpdateClientAddr +{ + public const UInt32 Base = 0x20_00_00_00; + + /// + /// ADDR: 0X00: 写Flash-读写地址——控制位
+ /// [31:16]: wr_sector_num
+ /// [15: 0]: {flash_wr_en,-,-,-, start_wr_sector}
+ ///
+ public const UInt32 WriteCtrl = Base + 0x00; + + /// + /// ADDR: 0X01: 写Flash-只写地址——FIFO入口
+ /// [31:0]: 写比特流数据入口
+ ///
+ public const UInt32 WriteFIFO = Base + 0x01; + + /// + /// ADDR: 0X02: 写Flash-只读地址——标志位
+ /// [31:24]: {-, -, -, -, -, -, -, wr_fifo_full}
+ /// [23:16]: {-, -, -, -, -, -, -, wr_fifo_empty}
+ /// [15: 8]: {-, -, -, -, -, -, -, flash_wr_done}
+ /// [ 7: 0]: {-, -, -, -, -, -, -, flash_clear_done}
+ ///
+ public const UInt32 WriteSign = Base + 0x02; + + /// + /// ADDR: 0X03: 读Flash-读写地址——控制位1
+ /// [31:16]: rd_sector_num
+ /// [15: 0]: {flash_rd_en,-,-,-, start_rd_sub_sector}
+ ///
+ public const UInt32 ReadCtrl1 = Base + 0x03; + + /// + /// ADDR: 0X04: 读Flash-读写地址——控制位2
+ /// [31:24]: { }
+ /// [23:16]: {-, -, -, -, -, -,{ bs_crc32_ok }}
+ /// [15: 8]: {-, -, -, -, -, -, -, crc_check_en}
+ /// [ 7: 0]: {-, -, -, -, -, -, -, bitstream_up2cpu_en}
+ ///
+ public const UInt32 ReadCtrl2 = Base + 0x04; + + /// + /// ADDR: 0X05: 读Flash-只读地址——FIFO出口
+ /// [31:0]: 读比特流数据出口
+ ///
+ public const UInt32 ReadFIFO = Base + 0x05; + + /// + /// ADDR: 0X06: 读Flash-只读地址——CRC校验值
+ /// [31:0]: CRC校验值 bs_readback_crc
+ ///
+ public const UInt32 ReadCRC = Base + 0x06; + + /// + /// ADDR: 0X07: 读Flash-只读地址——标志位
+ /// [31:24]: {-, -, -, -, -, -, -, rd_fifo_afull}
+ /// [23:16]: {-, -, -, -, -, -, -, rd_fifo_empty}
+ /// [15: 8]: {-, -, -, -, -, -, -, flash_rd_done}
+ /// [ 7: 0]: {-, -, -, -, -, -, -, bs_readback_crc_valid}
+ ///
+ public const UInt32 ReadSign = Base + 0x07; + + /// + /// ADDR: 0X08: 热启动开关-读写地址——控制位
+ /// [31: 8]: hotreset_addr
+ /// [ 7: 0]: {-, -, -, -, -, -, -, hotreset_en}
+ ///
+ public const UInt32 HotResetCtrl = Base + 0x08; + + /// + /// ADDR: 0X09: 只读地址 版本号
+ /// [31: 0]: FPGA_VERSION[31:0]
+ ///
+ public const UInt32 Version = Base + 0x09; +} + +static class FlashAddr +{ + public static readonly UInt32[] Switch = { 0xFFFF, 0x0000, 0x2000, 0x4000 }; + public static readonly UInt32[] Jump = { 0xFFFF, 0x1000, 0x3000, 0x5000 }; + public static readonly UInt32[] Bitstream = { 0x006000, 0x406000, 0x806000, 0xC06000 }; +} + + + +/// +/// [TODO:description] +/// +public class RemoteUpdateClient +{ + private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + + const int FLASH_SECTOR_LENGTH = 4 * 1024; + + readonly int timeout = 2000; + readonly int timeoutForWait = 60 * 1000; + + readonly int port; + readonly string address; + private IPEndPoint ep; + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:return] + public RemoteUpdateClient(string address, int port, int timeout = 2000, int timeoutForWait = 60 * 1000) + { + if (timeout < 0) + throw new ArgumentException("Timeout couldn't be negative", nameof(timeout)); + this.address = address; + this.port = port; + this.ep = new IPEndPoint(IPAddress.Parse(address), port); + this.timeout = timeout; + this.timeoutForWait = timeoutForWait; + } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:return] + private async ValueTask> WriteFlash(UInt32 flashAddr, int writeSectorNum, byte[] bytesData) + { + // Assert + if (writeSectorNum <= 0 || writeSectorNum >= FLASH_SECTOR_LENGTH) + return new(new ArgumentException( + $"Write sector num should be 1 ~ 4096, but given {writeSectorNum}", nameof(writeSectorNum))); + if (bytesData.Length % (4 * 1024) != 0) + return new(new ArgumentException( + $"The length of data should be divided by 4096, bug given {bytesData.Length}", nameof(bytesData))); + + { + var ret = await UDPClientPool.WriteAddr( + this.ep, RemoteUpdateClientAddr.WriteCtrl, + Convert.ToUInt32((writeSectorNum << 16) | (1 << 15) | Convert.ToInt32(flashAddr / 4096)), this.timeout); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value) return new(new Exception("Enable write flash failed")); + } + + { + var ret = await UDPClientPool.ReadAddrWithWait( + this.ep, RemoteUpdateClientAddr.WriteSign, + 0x00_00_00_01, 0x00_00_00_01, this.timeoutForWait); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value) return new(new Exception( + $"Flash clear failed after {this.timeoutForWait} milliseconds")); + } + + { + var ret = await UDPClientPool.WriteAddr(this.ep, RemoteUpdateClientAddr.WriteFIFO, bytesData, this.timeout); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value) return new(new Exception("Send data to flash failed")); + } + + { + var ret = await UDPClientPool.ReadAddrWithWait( + this.ep, RemoteUpdateClientAddr.WriteSign, + 0x00_00_01_00, 0x00_00_01_00, this.timeoutForWait); + if (!ret.IsSuccessful) return new(ret.Error); + return ret.Value; + } + } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:return] + private async ValueTask> EnableBitstream(int bitstreamNum) + { + // Assert + if (bitstreamNum <= 0 || bitstreamNum > 3) + return new(new ArgumentException( + $"Bitsteam num should be 1 ~ 3 for EnableBitstream, but given {bitstreamNum}", nameof(bitstreamNum))); + + var bytesData = new byte[FLASH_SECTOR_LENGTH]; + Array.Fill(bytesData, 0xFF); + bytesData[FLASH_SECTOR_LENGTH - 1] = 0x01; + bytesData[FLASH_SECTOR_LENGTH - 2] = 0x33; + bytesData[FLASH_SECTOR_LENGTH - 3] = 0x2D; + bytesData[FLASH_SECTOR_LENGTH - 4] = 0x94; + + + var ret = await WriteFlash(FlashAddr.Switch[bitstreamNum], 1, bytesData); + if (!ret.IsSuccessful) return new(ret.Error); + return ret.Value; + } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:return] + private async ValueTask> DisableBitstream(int bitstreamNum) + { + // Assert + if (bitstreamNum <= 0 || bitstreamNum > 3) + return new(new ArgumentException( + $"Bitsteam num should be 1 ~ 3 for DisableBitstream, but given {bitstreamNum}", nameof(bitstreamNum))); + + var bytesData = new byte[FLASH_SECTOR_LENGTH]; + Array.Fill(bytesData, 0xFF); + + var ret = await WriteFlash(FlashAddr.Switch[bitstreamNum], 1, bytesData); + if (!ret.IsSuccessful) return new(ret.Error); + return ret.Value; + } + + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:return] + private async ValueTask> WriteJumpCmd(int bitstreamNum) + { + // Assert + if (bitstreamNum <= 0 || bitstreamNum > 3) + return new(new ArgumentException( + $"Bitsteam num should be 1 ~ 3 for WriteJumpCmd, but given {bitstreamNum}", nameof(bitstreamNum))); + + // Init data + var bytesData = new byte[FLASH_SECTOR_LENGTH]; + for (int i = 3; i < FLASH_SECTOR_LENGTH; i += 4) + bytesData[i] = (byte)0xA0; + + { + var bytesSrc = new byte[] { 0x01, 0x00, 0x00, 0xAB }; + Buffer.BlockCopy(bytesSrc, 0, bytesData, 0x04, 4); + } + { + var bytesSrc = new byte[] { 0x0B, 0x00, 0x00, 0x00 }; + Buffer.BlockCopy(bytesSrc, 0, bytesData, 0x08, 4); + } + { + var bytesSrc = new byte[] { 0x01, 0x00, 0xC0, 0xAB }; + Buffer.BlockCopy(bytesSrc, 0, bytesData, 0x34, 4); + } + { + var bytesSrc = new byte[] { 0x00, 0x00, 0x00, 0x00 }; + Buffer.BlockCopy(bytesSrc, 0, bytesData, 0x38, 4); + } + { + var bytesSrc = new byte[] { 0x01, 0x00, 0x00, 0xAC }; + Buffer.BlockCopy(bytesSrc, 0, bytesData, 0x3C, 4); + } + { + var bytesSrc = Common.Number.NumberToBytes(FlashAddr.Bitstream[bitstreamNum], 4).Value; + Buffer.BlockCopy(Common.Number.ReverseBytes(bytesSrc, 4).Value, 0, bytesData, 0x40, 4); + } + { + var bytesSrc = new byte[] { 0x01, 0x00, 0x80, 0xA8 }; + Buffer.BlockCopy(bytesSrc, 0, bytesData, 0x44, 4); + } + { + var bytesSrc = new byte[] { 0x0F, 0x00, 0x00, 0x00 }; + Buffer.BlockCopy(bytesSrc, 0, bytesData, 0x48, 4); + } + + var ret = await WriteFlash(FlashAddr.Jump[bitstreamNum], 1, bytesData); + if (!ret.IsSuccessful) return new(ret.Error); + return ret.Value; + } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:return] + private async ValueTask> InitRemoteUpdate(int bitstreamNum) + { + // Assert + if (bitstreamNum < 0 || bitstreamNum > 3) + return new(new ArgumentException( + $"Bitsteam num should be 0 ~ 3 for InitRemoteUpdate, but given {bitstreamNum}", nameof(bitstreamNum))); + + for (int i = 1; i <= 3; i++) + { + { + var ret = (i == bitstreamNum) ? await EnableBitstream(i) : await DisableBitstream(i); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value) return new(new Exception($"Write switch {i} failed")); + } + + { + var ret = await WriteJumpCmd(i); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value) return new(new Exception($"Write jump {i} failed")); + } + } + + return true; + } + + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:return] + private async ValueTask> CheckBitstreamCRC(int bitstreamNum, int bitstreamLen, UInt32 checkSum) + { + { + var ret = await UDPClientPool.WriteAddr(this.ep, RemoteUpdateClientAddr.ReadCtrl2, 0x00_00_00_00, this.timeout); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value) return new(new Exception("Write read control 2 failed")); + } + + { + var ret = await UDPClientPool.WriteAddr( + this.ep, RemoteUpdateClientAddr.ReadCtrl1, + Convert.ToUInt32((bitstreamLen << 16) | (1 << 15) | Convert.ToInt32(FlashAddr.Bitstream[bitstreamNum] / 4096)), + this.timeout); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value) return new(new Exception("Write read control 1 failed")); + } + + { + var ret = await UDPClientPool.ReadAddrWithWait( + this.ep, RemoteUpdateClientAddr.ReadSign, + 0x00_00_01_00, 0x00_00_01_00, this.timeoutForWait); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value) return new(new Exception( + $"Read bitstream failed after {this.timeoutForWait} milliseconds")); + } + + { + var ret = await UDPClientPool.ReadAddr(this.ep, RemoteUpdateClientAddr.ReadCRC, this.timeout); + if (!ret.IsSuccessful) return new(ret.Error); + + var bytes = ret.Value.Options.Data; + if (bytes is null) + return new(new Exception("CRC is null")); + + var remoteCRC = Common.Number.BytesToUInt32(bytes); + if (!remoteCRC.IsSuccessful) return new(remoteCRC.Error); + + logger.Debug($"Bitstream {bitstreamNum} Expected CRC: \t 0x{Convert.ToString(checkSum, 16)}"); + logger.Debug($"Bitstream {bitstreamNum} Received CRC: \t 0x{Convert.ToString(remoteCRC.Value, 16)}"); + + return remoteCRC.Value == checkSum; + } + } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:return] + private async ValueTask> HotReset(int bitstreamNum) + { + // Assert + if (bitstreamNum < 0 || bitstreamNum > 3) + return new(new ArgumentException( + $"Bitsteam num should be 0 ~ 3 for HotRest, but given {bitstreamNum}", nameof(bitstreamNum))); + + var ret = await UDPClientPool.WriteAddr( + this.ep, RemoteUpdateClientAddr.HotResetCtrl, + ((FlashAddr.Bitstream[bitstreamNum] << 8) | 1), this.timeout); + if (!ret.IsSuccessful) return new(ret.Error); + return ret.Value; + } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:return] + public async ValueTask> HotResetBitstream(int bitstreamNum) + { + await MsgBus.UDPServer.ClearUDPData(this.address); + logger.Trace("Clear udp data finished"); + + { + var ret = await InitRemoteUpdate(bitstreamNum); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value) return new(new Exception("Init remote update failed")); + } + + { + var ret = await HotReset(bitstreamNum); + if (!ret.IsSuccessful) return new(ret.Error); + return ret.Value; + } + } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:return] + public async ValueTask> UploadBitstreams( + byte[]? goldenBitream, + byte[]? bitstream1, + byte[]? bitstream2, + byte[]? bitstream3) + { + await MsgBus.UDPServer.ClearUDPData(this.address); + logger.Trace("Clear udp data finished"); + + for (int bitstreamNum = 0; bitstreamNum < 4; bitstreamNum++) + { + byte[] bytesData; + if (bitstreamNum == 0 && goldenBitream is not null) + bytesData = goldenBitream; + else if (bitstreamNum == 1 && bitstream1 is not null) + bytesData = bitstream1; + else if (bitstreamNum == 2 && bitstream2 is not null) + bytesData = bitstream2; + else if (bitstreamNum == 3 && bitstream3 is not null) + bytesData = bitstream3; + else continue; + + var bitstreamBlockNum = bytesData.Length / (4 * 1024); + + { + var ret = await WriteFlash(FlashAddr.Bitstream[bitstreamNum], bitstreamBlockNum, bytesData); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value) return new(new Exception("Write bitstream failed")); + } + + { + var crc = Honoo.IO.Hashing.Crc.Create(Honoo.IO.Hashing.CrcName.CRC32_MPEG_2); + var checkSum = crc.ComputeFinal(bytesData); + var ret = await CheckBitstreamCRC(bitstreamNum, bitstreamBlockNum, checkSum.ToUInt32()); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value) logger.Warn($"Bitstream {bitstreamNum} CRC32 not correct!"); + else logger.Info($"Bitstream {bitstreamNum} CRC32 calibration passed"); + } + + } + + return true; + } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:return] + public async ValueTask> UpdateBitstream(int bitstreamNum, byte[] bytesData) + { + if (bytesData.Length % (4 * 1024) != 0) + return new(new ArgumentException( + $"The length of data should be divided by 4096, bug given {bytesData.Length}", nameof(bytesData))); + var bitstreamBlockNum = bytesData.Length / (4 * 1024); + + await MsgBus.UDPServer.ClearUDPData(this.address); + logger.Trace("Clear udp data finished"); + + { + var ret = await InitRemoteUpdate(bitstreamNum); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value) return new(new Exception("Init remote update failed")); + } + + { + var ret = await WriteFlash(FlashAddr.Bitstream[bitstreamNum], bitstreamBlockNum, bytesData); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value) return new(new Exception("Write bitstream failed")); + } + + { + var crc = Honoo.IO.Hashing.Crc.Create(Honoo.IO.Hashing.CrcName.CRC32_MPEG_2); + var checkSum = crc.ComputeFinal(bytesData); + var ret = await CheckBitstreamCRC(bitstreamNum, bitstreamBlockNum, checkSum.ToUInt32()); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value) logger.Warn("CRC32 not correct!"); + else logger.Info("CRC32 calibration passed"); + } + + { + var ret = await HotReset(bitstreamNum); + if (!ret.IsSuccessful) return new(ret.Error); + return ret.Value; + } + } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:return] + public async ValueTask> UpdateBitstreams( + int enableBitstreamNum, + byte[]? goldenBitream, + byte[]? bitstream1, + byte[]? bitstream2, + byte[]? bitstream3) + { + // Assert + if (goldenBitream is null && bitstream1 is null && bitstream2 is null && bitstream3 is null) + return new(new ArgumentException( + $"At least one bitstream should not be empty")); + if ((enableBitstreamNum == 0 && goldenBitream is null) || + (enableBitstreamNum == 1 && bitstream1 is null) || + (enableBitstreamNum == 2 && bitstream2 is null) || + (enableBitstreamNum == 3 && bitstream3 is null)) + return new(new ArgumentException($"Bitstream {enableBitstreamNum} shouldn't be empty")); + + { + var ret = await UploadBitstreams(goldenBitream, bitstream1, bitstream2, bitstream3); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value) return false; + } + + { + var ret = await HotResetBitstream(enableBitstreamNum); + if (!ret.IsSuccessful) return new(ret.Error); + return ret.Value; + } + } + +} diff --git a/server/src/UdpClientPool.cs b/server/src/UdpClientPool.cs index c86aab2..2c59a56 100644 --- a/server/src/UdpClientPool.cs +++ b/server/src/UdpClientPool.cs @@ -1,6 +1,8 @@ using System.Net; using System.Net.Sockets; using System.Text; +using DotNext; +using WebProtocol; /// /// UDP客户端发送池 @@ -161,4 +163,228 @@ public class UDPClientPool return isSuccessful; } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:return] + public static async ValueTask> ReadAddr( + IPEndPoint endPoint, uint devAddr, int timeout = 1000) + { + var ret = false; + var opts = new SendAddrPackOptions(); + + opts.BurstType = BurstType.FixedBurst; + opts.BurstLength = 0; + opts.CommandID = 0; + opts.Address = devAddr; + + // Read Jtag State Register + opts.IsWrite = false; + ret = await UDPClientPool.SendAddrPackAsync(endPoint, new SendAddrPackage(opts)); + if (!ret) return new(new Exception("Send Address Package Failed!")); + + // Wait for Read Ack + if (!MsgBus.IsRunning) + return new(new Exception("Message Bus not Working!")); + + var retPack = await MsgBus.UDPServer.WaitForDataAsync( + endPoint.Address.ToString(), endPoint.Port, timeout); + if (!retPack.IsSuccessful) return new(retPack.Error); + else if (!retPack.Value.IsSuccessful) + return new(new Exception("Send address package failed")); + + var retPackOpts = retPack.Value.Options; + if (retPackOpts.Data is null) + return new(new Exception($"Data is Null, package: {retPackOpts.ToString()}")); + + return retPack; + } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:return] + public static async ValueTask> ReadAddr( + IPEndPoint endPoint, uint devAddr, UInt32 result, UInt32 resultMask, int timeout = 1000) + { + var address = endPoint.Address.ToString(); + + var ret = await ReadAddr(endPoint, devAddr, timeout); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value.IsSuccessful) + return new(new Exception($"Read device {address} address {devAddr} failed")); + + var retData = ret.Value.Options.Data; + if (retData is null) + return new(new Exception($"Device {address} receive none")); + if (retData.Length != 4) + return new(new Exception( + $"Device {address} receive data is {retData.Length} bytes instead of 4 bytes")); + + // Check result + try + { + var retCode = Convert.ToUInt32(Common.Number.BytesToUInt64(retData).Value); + return Common.Number.BitsCheck(retCode, result, resultMask); + } + catch (Exception error) + { + return new(error); + } + } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:return] + public static async ValueTask> ReadAddrWithWait( + IPEndPoint endPoint, uint devAddr, UInt32 result, UInt32 resultMask, int timeout = 1000) + { + var address = endPoint.Address.ToString(); + + var startTime = DateTime.Now; + while (true) + { + var elapsed = DateTime.Now - startTime; + if (elapsed >= TimeSpan.FromMilliseconds(timeout)) break; + var timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed; + + try + { + var ret = await ReadAddr(endPoint, devAddr, Convert.ToInt32(timeleft.TotalMilliseconds)); + if (!ret.IsSuccessful) return new(ret.Error); + if (!ret.Value.IsSuccessful) + return new(new Exception($"Read device {address} address {devAddr} failed")); + + var retData = ret.Value.Options.Data; + if (retData is null) + return new(new Exception($"Device {address} receive none")); + if (retData.Length != 4) + return new(new Exception( + $"Device {address} receive data is {retData.Length} bytes instead of 4 bytes")); + + // Check result + var retCode = Convert.ToUInt32(Common.Number.BytesToUInt64(retData).Value); + if (Common.Number.BitsCheck(retCode, result, resultMask)) return true; + } + catch (Exception error) + { + return new(error); + } + } + + return false; + } + + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:return] + public static async ValueTask> WriteAddr( + IPEndPoint endPoint, UInt32 devAddr, UInt32 data, int timeout = 1000) + { + var ret = false; + var opts = new SendAddrPackOptions(); + + opts.BurstType = BurstType.FixedBurst; + opts.BurstLength = 0; + opts.CommandID = 0; + opts.Address = devAddr; + + // Write Jtag State Register + opts.IsWrite = true; + ret = await UDPClientPool.SendAddrPackAsync(endPoint, new SendAddrPackage(opts)); + if (!ret) return new(new Exception("Send 1st address package failed!")); + // Send Data Package + ret = await UDPClientPool.SendDataPackAsync(endPoint, + new SendDataPackage(Common.Number.NumberToBytes(data, 4).Value)); + if (!ret) return new(new Exception("Send data package failed!")); + + // Check Msg Bus + if (!MsgBus.IsRunning) + return new(new Exception("Message bus not working!")); + + // Wait for Write Ack + var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync( + endPoint.Address.ToString(), endPoint.Port, timeout); + if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error); + + return udpWriteAck.Value.IsSuccessful; + } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:return] + public static async ValueTask> WriteAddr(IPEndPoint endPoint, UInt32 devAddr, byte[] dataArray, int timeout = 1000) + { + var ret = false; + var opts = new SendAddrPackOptions(); + + + opts.BurstType = BurstType.FixedBurst; + opts.CommandID = 0; + opts.Address = devAddr; + + // Check Msg Bus + if (!MsgBus.IsRunning) + return new(new Exception("Message bus not working!")); + + opts.IsWrite = true; + var hasRest = dataArray.Length % (256 * (32 / 8)) != 0; + var writeTimes = hasRest ? + dataArray.Length / (256 * (32 / 8)) + 1 : + dataArray.Length / (256 * (32 / 8)); + for (var i = 0; i < writeTimes; i++) + { + // Sperate Data Array + var isLastData = i == writeTimes - 1; + var sendDataArray = + isLastData ? + dataArray[(i * (256 * (32 / 8)))..] : + dataArray[(i * (256 * (32 / 8)))..((i + 1) * (256 * (32 / 8)))]; + + // Write Jtag State Register + opts.BurstLength = ((byte)(sendDataArray.Length / 4 - 1)); + ret = await UDPClientPool.SendAddrPackAsync(endPoint, new SendAddrPackage(opts)); + if (!ret) return new(new Exception("Send 1st address package failed!")); + + // Send Data Package + ret = await UDPClientPool.SendDataPackAsync(endPoint, new SendDataPackage(sendDataArray)); + if (!ret) return new(new Exception("Send data package failed!")); + + // Wait for Write Ack + var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(endPoint.Address.ToString(), endPoint.Port, timeout); + if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error); + + if (!udpWriteAck.Value.IsSuccessful) + return false; + } + + return true; + } + } diff --git a/server/src/WebProtocol.cs b/server/src/WebProtocol.cs index 707a7e1..67f542a 100644 --- a/server/src/WebProtocol.cs +++ b/server/src/WebProtocol.cs @@ -248,7 +248,7 @@ namespace WebProtocol )); } - var address = Common.Number.BytesToNumber(bytes[4..]).Value; + var address = Common.Number.BytesToUInt64(bytes[4..]).Value; return new SendAddrPackage(bytes[1], bytes[2], Convert.ToUInt32(address)); } }