# simpleSender **Repository Path**: aquawius/simple-sender ## Basic Information - **Project Name**: simpleSender - **Description**: 一个简单地VHDL数据生成器 - **Primary Language**: C - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-07-25 - **Last Updated**: 2023-08-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # VHDL Sender 作者是 @aquawius, 多多指教. ###### 项目代码可以随意复制, 不设置开源协议. --- > 这个项目作为 VHDL 项目的一部分,承载一个产生数据的作用. > 这里有一个很重要的问题: 我的对端知不知道我的线路的配置? 开会后: 得到的结果为`"是"`,那么为什么还要向数据块中填充这些看似没有必要的数据呢? 先做好了. 我的设计大致分为下面的几个点: > 注意: 如果要产生数据应当开辟相应数据类型的完整空间. > > 7.27: 已经全面使用 malloc 和 free 技术栈. ~~(妈妈再也不用担心我的栈空间用完了.......)~~ > > 原程序命名为 simpleSender, 使用 malloc 的版本为 simpleSenderReborn. ##### 1. 产生数据 这里应该有几个相应的函数,包含下面几种数据格式:`GENERATE_DATA_TYPE` > 0. 固定值类型数据 `FIXED` : > 1. 低 2 字节初值为 0 并循环递增 ,高 2 字节为逻辑通道号固定不变 > 2. 4 个字节初值为 0,每次递增值为逻辑通道号的值 > 3. 自定义数据格式(可选) **这里就产生了一个分叉, 如果数据在内存中直接生成, 而不需要输出文件. 那么下面的 generate 程序没有必要做.(因为生成的数据块直接拆分是更加方便的)** 这里可以考虑产生对应的内存块. **完成进度:** - [x] **_在内存中生成数据这个问题已经完成了._** 生成固定数据的函数 generatePersistData_XXXXXX()系列函数 ```c size_t generatePersistData_60A1DA8E(char *buffer, size_t bufferLength, size_t filledLength) size_t generatePersistData_00toFF(char *buffer, size_t bufferLength, size_t filledLength) ``` 还有一些函数没有写, 看具体要求. (反正挺好写的, 就是产生数据块呗, 两个比较典型的函数都写了). 有几个工具函数, 用于填充空间的, malloc 默认分配的空间内容是随机的, 填充后容易 debug. ```c size_t fillBlockWithZero(void *buffer, size_t filledLength) size_t fillBlockWithSpecificValue(void *buffer, size_t filledLength, uint8_t value) ``` 其中可能需要翻转字节序的, 有下面的工具函数. ```c bool reverseBufferInPlace(char *buffer, size_t length) void reverseBufferWithExtraBuffer(const char *originBuffer, size_t length, char *outBuffer) ``` --- ##### 2. 包装数据 我使用的数据包装如下: `HEADER, LOAD`为一个整体数据,应当创建对应的包装和解包装函数. > LOAD 是 VC-x 中承载的数据. > > `FULL`中包含: `HEADER` `BODY`和 `LOAD`. > > > > `HEADER`中包含有 Frame 头信息: `logicalChannelID; physicalChannelID; SQ; vcType; frameLength; 等等` > > `BODY`含有几个内容: MFI + `LOAD` > > `LOAD`包含内容数据. VC-4的LOAD为2340Bytes, VC-12为136Bytes. > > > > 总之 HEADER = (header特有的东西) + BODY, > > ​ BODY = MFI(body特有的东西) + LAOD, > > ​ LOAD = 承载的数据. ~~其中因为定义不明确, `LOAD`部分可能不一致. (**但是我都用宏的方式定义了,后期修改难度不大**).~~ > **注意: 因为编译器优化的原因, sizeof(HEADER) + sizeof(LOAD) != sizeof(FULL)!!!!!!!** > > **就是因为这个玩应, 我在调试的时候踩了大坑, 足足浪费了老子一天的时间修改函数, 最终发现是编译器的锅.** > > **得到了老师的场外援助: `__attribute__((packed))`可以通知编译器内存不对齐. 还是经验多的好啊......** 来自**操某同学**的启示. > 尽量将`char`类型的数据写成`unsigned char`, 后来我想着一步到位, 直接全变成`uint8_t`了 ~~这里可能需要设计多个函数,因为有可能包装数据采用的包装壳并不一样,有些数据需要完整的数据壳 (也就是指 HEADER), 有一些可能只需要包含 MFI 信息就可以了 (也就是 BODY).~~ 我这里直接使用的就是 行数量+ HEADER+BODY(s) 的形式, 后续有需求再说嘛. (~~其实因为这块模块化不好化~~) 要想从上面的块数据变成字节间插的数据块. 大致有这么几个可以讲的: 1. 数据块不一定是 vc-x 可以承载的 frame 的整数倍. 那么需要多`SQ`个 frame 存放数据. 后面没有用到的数据块需要全零填充. 为了测试使用, 这里全部使用填充 BB 的形式. 2. 这里每个 frame 采用全量冗余的方案. 即每个 frame 存储的都是 HEADER 的类型. 后续再调整怎样减少冗余. 3. ~~文件块的大小必须包含在 frame 中.~~ 这个待定. 4. ~~字节间插这块实现是一个难点. 尤其还要全部变量化.~~ 这个完成了. *** 实现的思路如下: 1. 根据数据长度得到需要数据块的个数/长度. `getFrameNumFromBufferLength(), getFrameNumOneLineFromBufferLength()` 对于已经生成的数据块,我们只知道这个数据块的长度. 因此需要一个函数, 接受`数据块长度`,`SQ`和`要生产的数据包装类型`,得到`生成的数据包的数量`, `生成数据包的数量 / SQ`可以得到单行 SQ 对应的 frame 数量. ![image-20230721143926520](./readme.assets/image-20230721143926520.png) 注意: 这里产生的数据包数量的计算已经包含全部开销部分(每个 Frame 全部为 HEADER). 换句话说 这里仅针对有效载荷数据进行计算! - [x] **_根据数据长度得到需要数据块的个数/长度完成_** 工具类函数: ```c size_t getFrameNumOneLineFromBufferLength(size_t bufferLength, enum VC_TYPE vcType, uint16_t splitedCols) size_t getFrameNumOneLineFromOneLineLength(enum VC_TYPE vcType, size_t oneLineLength) size_t getFrameNumFromBufferLength(size_t bufferLength, enum VC_TYPE vcType, uint16_t splitedCols) size_t getTotalFullLengthFormBufferLength(size_t bufferLength, enum VC_TYPE vcType, uint16_t splitedCols) size_t getTotalFullLengthOneLineFromBufferLength(size_t bufferLength, enum VC_TYPE vcType, uint16_t splitedCols) ``` 2. 根据数据块个数分配空间. 根据(1)步的数据块分配. 根据上一步产生的数据, 我们已经知道了应该开辟多少空间给一个数据块了. ~~这里不考虑栈溢出的问题, 优先使用数组的方式开辟空间.~~ > 7.26: 不能这样做, 这样在处理大文件的时候, 会无法正常运行, 原因是 Windows 平台默认每个线程默认只有 1MB 的栈空间. 一定要使用 malloc/alloc 和 free 的技术栈. - [x] **_开辟数据完成_** 通过(1)中列出的函数, 能够获得需要开辟的空间大小. 调用填充之前使用 malloc 就好. 3. 填充对应的数据. - [x] **_填充数据完成_** 有这个函数: 将已经产生的数据块填充到准备输出成文件的数据块.(这里不填充 HEADER,留着没动) ```c size_t splitIntoMutipleBuffer(const char *buffer, size_t bufferLength, enum VC_TYPE vcType, uint16_t splitedCols,size_t outBufferOneLineLength, char outBuffer[splitedCols][outBufferOneLineLength]) ``` 4. 将 Frame 填充到已经分配的空间. - [x] **_生成数据头_** ```c vcTypeHeader_t generateHeaderWithSpecificValue(enum VC_TYPE vcType, uint16_t logicalChannelID, uint16_t physicalChannelID, uint16_t SQ,size_t frameLength, uint16_t frameNum, uint16_t MFI) void fillHeaderWithSpecificValue(vcTypeHeader_t *target, enum VC_TYPE vcType, uint16_t logicalChannelID, uint16_t physicalChannelID, uint16_t SQ, size_t frameLength, uint16_t frameNum, uint16_t MFI) ``` - [x] **_填充 Frame 头_** ```c void fillHeadersToSplitBuffer(vcTypeHeader_t startHeader, enum VC_TYPE vcType, uint16_t splitedCols, size_t inputBufferOneLineLength, char inputBuffer[splitedCols][inputBufferOneLineLength]) ``` 5. 这几个分配的空间就是我们想要得到的最终的文件, 产生文件输出即可. - [x] **_输出到文件_** ```c bool outputToFiles(uint16_t splitedCols, size_t inputBufferOneLineLength, char inputBuffer[splitedCols][inputBufferOneLineLength]) ``` **额外的东西:** - [x] 修改指定 Frame 的 HEADER: 指定 Row 和 Col(都以 0 为 Index 开始) 即可填充指定的 Frame 数组. (未测试) ```c bool fillHeaderToSplitFrameBuffersWithColIndex(vcTypeHeader_t headerData, enum VC_TYPE vcType, uint16_t splitedCols, size_t inputBufferOneLineLength, char inputBuffer[splitedCols][inputBufferOneLineLength], size_t targetRowFrameIndex, size_t targetColFrameIndex) ``` - [x] 查看 Header 是什么: ```c void printVCTypeHeader(const vcTypeHeader_t *header) ``` - [x] 查看产生的内容: ```c // 没有拆分的用这个 void printHeapBufferMemory(const void *mallocPtr, size_t bufferSize) // 拆分之后的用这个 void printHeapOutterBufferContent(enum VC_TYPE vcType, uint16_t splitedCols, size_t bufferOneLineLength, char outBuffer[splitedCols][bufferOneLineLength]) ``` ###### 现在还需要做什么: 1. 数据验证: 我怎么知道我的数据产出的对不对? 2. 变换 VC 类型: 现在主要使用 VC_4, 好像产生的数据是正确的. 还有 VC_12 没有测试. 3. 写一个能够接受参数的东西. (我倒是觉得这个会影响产生数据的多样性, ~~除非有肝把所有要生成的所有东西全部肝了~~) 4. 写一个接受/解析端. > 我认为当下应当以数据的正确性验证为重. 7/27 半夜 00:50 --- > 7.29 晚上 22:35 更新: > > ###### 现在还需要做什么: > > 1. 数据验证: 我怎么知道我的数据产出的对不对? > > > _update: 这个放在接收端来验证, 即使我已经通过看生成文件的 hex 知道大概没有问题._ > > 2. 变换 VC 类型: 现在主要使用 VC_4, 好像产生的数据是正确的. 还有 VC_12 没有测试. > > > _update: VC_4 和 VC_12 都测试完成, 没有问题._ > > 3. 写一个能够接受参数的东西. (我倒是觉得这个会影响产生数据的多样性, ~~除非有肝把所有要生成的所有东西全部肝了~~) > > > _update: 写了, 全当是锻炼自己了, 还有一些不确定的因素, 尤其是关于 VC_12 和 VC_4 组合应该产生几个文件的疑惑, 有可能还得大改._ > > > > _但是我不是很慌, 反正全部都是模块化. (刚开始写起来比较头疼 (要考虑怎样设计各个函数), 现在还好.)_ > > > > > 多了下面几个函数和结构. > > > > > `struct strTypeParameter`和`struct numTypeParameter` 这两个用于接受传入参数, str 是原始内容, num 是转换后数字和 enum 内容. > > > > > > ```c > > > void printNumTypeParameterContent(struct numTypeParameter numParameter); > > > // 打印 struct numTypeParameter的内容. > > > > > > void printHelpMessage(char *argv[0]); > > > // 打印帮助信息, 使用选项--help或者-h调用. > > > > > > struct strTypeParameter parameterPasserToStrsType(int argc, char *argv[]); > > > // 使用getopt_long函数, 将传入参数转换成strTypeParameter. > > > // strTypeParameter将会交给valdatingParameterStrToNumType()函数处理成numTypeParameter变成其他函数调用的参数 > > > > > > struct numTypeParameter valdatingParameterStrToNumType(struct strTypeParameter strParameter); > > > // 将strTypeParameter内容做验证, 失败直接退出 ,成功后转换成numTypeParameter, 供后续函数调用. > > > > > > bool extractVCTypeValues(const char *inputString, int *n1, int *n2); > > > // valdatingParameterStrToNumType调用链的一环, 将VC-n1-n2v转换成两个数字 n1,n2, 通过形参返回. > > > > > > ``` > > 6. 写一个接受/解析端. > > 有想法, 但是在生成文件数量没有解决之前, 我还不能开这个东西. > > --- > > **有疑惑的东西:** > > > 因为物理通道是确定的, 理论上说应该仅仅生成理论通道数量的文件. > > > > 但是因为我模块化的原因, 想要生成 VC-4-3v 需要开 3 个文件, 想要生成 VC-12-4v 需要开 4 个文件. > > 也就是说我现在的结构生成的文件数量和 VC-n-Xv 中的 X 形成了依赖的关系. 我不知道这样是否正确. > > 如果正确还好, 不正确或许要加上好多传参, 至少得把物理通道数和逻辑通道数都放到`fill......`还有`generate......`那一套函数中处理. > > >差分延迟这块还是不是很懂(这里是指不知道怎样用代码实现), 基本原理是了解力. > > > >![image-20230729235048961](./readme.assets/image-20230729235048961.png) > > --- > > **还少什么东西:** > > > 1. 你可以指定`-g`或`--gentype`为 `INPUTFILE`, `-i`或`--input` 指定文件路径, 将生成的数据用文件内容替换. > > > > 但是这部分内容我还没有写. > > > 2. 填充文件头的时候, 和输出文件的时候 physicalID 暂时是写成一个固定值. 这里可能需要判断一些东西, 具体怎样一个流程我还没有想好. > > > 3. 现在程序仅仅有处理参数和生成文件两种能力, 而且没有联调测试, 所以你现在使用它仅仅只有参数解析而已. > > > 4. 生成文件名字的时候不是按照物理通道的数量命名的. > > > 5. 后续等着测试好了. > > _暂时想到的就这些._ > > > 现有环境测试, 你可以使用这个参数进行测试: > ```shell > .\simpleSenderReborn.exe --vctype VC-12-3v --gentype asdf --genlength 99999 --genframenum 30 --physicalid 32 --logicalid 30 --startmfi 10 --inputfile abc.txt > # 程序将不会有任何动作, 仅仅完成参数解析. > ``` > > currTime: 23:21 7/29 --- Update 13:53 2/8/2023 > 基本快要完成了... > > 主要完成了选项参数和生成函数的合并. 并且加入了 `--structuremode`选项. 上面的函数有改动, 重新说一下, 主要的使用思路如下: # 使用说明 ### 0x00 综述 程序输入参数, 指定生成模式`--structuremode`, 生成的数据类型`--gentype`, 起始物理通道id`--startphysicalid`, 起始逻辑通道id`--startlogicalid`, 起始MFI值`--startmfi`后, 就可以生成指定类别的数据. 程序运行结束产生的数据命名为物理通道名, 类似于:`Data_PhysicalChannel_6.dat`, 表示生成的内容是物理通道为6的数据. > 你可以通过 `--help` 选项查看对应的参数帮助文档. --- ### 0x01 参数输入 ##### 结构模式 structuremode 其中输入参数类别分成两个大类: 1. **使用预设的方式产生数据:** 2. **单独产生数据** > 1. 使用预设产生数据指的是`--structuremode`为 0,1,2....512的值, 也就是PDF中指定的那些值. > > > 0~255:全 VC 4模式(10G SDH中包含 64个 VC 4) > > 0: 全 VC 4模式,每个 VC 4为一个单独的逻辑通道 > > 1:全 VC 4模式, 64个 VC 4组合成 1个逻辑通道 > > 2:全 VC 4模式, ,32个 VC 4组合成 1个逻辑通道,其它每个 VC 4为独立的逻辑通道 > > > > 256: ...... > > > > 512: ...... 详细参看PDF说明文档. > > 这里贴出使用的几个命令参数范例. > > ```shell > ./simpleSenderReborn --structuremode 0 --gentype 00toff --genframenum 30 --startphysicalid 0 --startlogicalid 0 --startmfi 100 > > ./simpleSenderReborn --structuremode 256 --gentype 00toff --genframenum 30 --startphysicalid 0 --startlogicalid 0 --startmfi 100 > > ./simpleSenderReborn --structuremode 512 --gentype 00toff --genframenum 126 --startphysicalid 0 --startlogicalid 0 --startmfi 100 > ``` > > > **请注意: 请务必保证输入的`--genframenum`能够整除`--structuremode`指定的`splitChannel`(也就是VC-4-Xv中的X值), 否则将会出现填充不完整的情况(我已经考虑这种情况,并将其视为正常的需要产生的数据情况), 后续不完整填充的内容将会填充16进制字节'BB'.** > > > > 在这个模式中**没有整除校验**. > 2. 单独产生数据 > > 将会产生一个逻辑通道的数据(但是可以是多个物理通道) > > > **注意: 需要特别指定 `--structuremode 9999` 才会生效!!** > > > 你必须指定`--vctype`才能正常输出单个逻辑通道文件. **`--vctype`的格式为 `VC-N-Xv`,其中VC为大写, v为小写, N和X分别表示VC类型 和 拆分通道数.** > > 下面是一个参数示例: > > ```shell > .\simpleSenderReborn -S 9999 --vctype VC-4-12v --gentype 00toff --genframenum 120 --startphysicalid 0 --startlogicalid 0 --startmfi 100 > ``` > > > 同样的, **请注意: 请务必保证输入的`--genframenum`能够整除`--structuremode`指定的`splitChannel`(也就是VC-4-Xv中的X值)** > > > > 这个模式中**存在整除校验**, 如果不能整除将禁止运行. ##### 产生数据类型 genType 你可以通过 `--gentype`参数指定生成的数据类型, 我目前仅仅写了两种生成模式, 还有一种模式处于不开放状态. 1. `--gentype 00toff` (默认) > 顾名思义, 从00一直累加到FF, 然后回到00, 不停循环的一种填充模式. > > 注意: 如果出现了未识别的数据类型, 程序将打印一行以 `WARN:` 开头的警告. 并自动将错误的生成数据类型切换为00toff. 2. `--gentype 60a1da8e` >同样顾名思义, 填充的数据写死为60A1DA8E, 如果frame剩余空间不能完全容纳这4个字节, 能填充几个字节则填充几个字节. 3. ~~`--gentype inputfile `~~ **(暂未开放)** > ~~如果`--gentype`指定为inputfile, 那么需要增加参数`--inputfile `, 将生成的数据流替换为FILEPATH目标文件的文件流.~~ ##### 产生数据块长度 genframenum 和 genlength > **注意: 实际使用中应当以genframenum为主.** 其实这个参数涉及到一些我当初的程序设计历史问题. (但这个选项我还是保留了, 嘻嘻). 我在当初设计生成数据的程序的时候使用的基本单位是genlength. 我认为他更灵活, 生成数据可以精确到字节位. 这样可以在写接受端程序的时候能完整还原出文件长度. 但是实际使用好像并不是这样的, 要求以Frame为单位生成数据...... 所以, 本质上, 我是通过genframenum来计算需要的genlength空间, 然后调用那些其他的函数进行数据产生的. 我想保留生成程序最大的灵活性, 所以将这选项保留了下来. > 如果你在生成的数据中看到的一堆'BB'字节出现在文件的末尾, 多半是你的参数有问题, 因为genlength填充完毕之后因为没有可用的数据继续填充而停止, 后边就全部变成了BB(这些BB是在内存中开始分配就已经填充了,DEBUG用). ##### 起始物理ID/逻辑ID 和起始MFI startphysicalid/startlogicalid/startmfi > 注意: 这个值仅仅作为起始ID用. 举个例子: 我想生成一个 VC-12-3v的数据. 起始物理ID为10, 起始逻辑ID为20. 命令如下: ```shell .\simpleSenderReborn.exe -S 9999 --vctype VC-12-3v --gentype 00toff --genframenum 120 --startphysicalid 10 --startlogicalid 20 --startmfi 100 ``` 那么生成的文件的数据头将会是这样的: ```shell Data_PhysicalChannel_10.dat: HEADER: 14 00 0A 00 00 00 98 00 28 00 0C 00 00 00 logicalChannelID: 20 physicalChannelID: 10 SQ: 0 frameLength: 152 frameNum: 40 vcType: VC_TYPE12 Data_PhysicalChannel_11.dat: HEADER: 14 00 0B 00 01 00 98 00 28 00 0C 00 00 00 logicalChannelID: 20 physicalChannelID: 11 SQ: 1 frameLength: 152 frameNum: 40 vcType: VC_TYPE12 Data_PhysicalChannel_12.dat: HEADER: 14 00 0C 00 02 00 98 00 28 00 0C 00 00 00 logicalChannelID: 20 physicalChannelID: 12 SQ: 2 frameLength: 152 frameNum: 40 vcType: VC_TYPE12 ``` > 因为生成的数据为一个逻辑通道的数据, 所以逻辑ID `logicalChannelID: 20` 将会一直是20不变. > > 物理ID递增 是因为生成的拆分通道是3, 所以物理ID的值为 10,11,12. MFI的值在生成的文件中应该是这样的: 起始MFI为100, 对应uint16_t为`64 00`. ```shell HEADER MFI FRAME MFI FRAME MFI .... 14 00 0A 00 00 00 98 00 28 00 0C 00 00 00 64 00 ~ 65 00 ~ 66 00 .... 14 00 0B 00 01 00 98 00 28 00 0C 00 00 00 64 00 ~ 65 00 ~ 66 00 .... 14 00 0C 00 02 00 98 00 28 00 0C 00 00 00 64 00 ~ 65 00 ~ 66 00 .... ``` ### 0x02 程序执行流程 有点多,不想写了...... QwQ