2009-05-04 87 views
1

这是一个相当长的问题,所以请和我一起裸照。你会如何解决这个数据解析问题?

我们正在为一块硬件实现一个模拟器,该硬件同时正在开发中。这个想法是给第三方 一个软件解决方案来测试他们的客户端软件,并给开发人员一个参考点来实现他们的固件。

编写硬件协议的人员使用名为INCA_XDR的定制的SUN XDR的 版本。这是一个序列化和反序列化消息的工具。它用C语言编写,我们希望避免任何 本地代码,因此我们正在手动解析协议数据。

该协议是由性质相当复杂和数据分组 可以具有许多不同的结构,但它总是具有相同的全局结构:

[HEAD] [INTRO] [DATA] [TAIL]

[HEAD] = 
    byte sync 0x03 
    byte length X  [MSB]  X = length of [HEADER] + [INTRO] + [DATA] 
    byte length X  [LSB]  X = length of [HEADER] + [INTRO] + [DATA] 
    byte check X  [MSB]  X = crc of [INTRO] [DATA] 
    byte check X  [LSB]  X = crc of [INTRO] [DATA] 
    byte headercheck X    X = XOR over [SYNC] [LENGTH] [CHECK] 

[INTRO] 
    byte version 0x03 
    byte address X     X = 0 for point-to-point, 1-254 for specific controller, 255 = broadcast 
    byte sequence X     X = sequence number 
    byte group X  [MSB]  X = The category of the message 
    byte group X  [LSB]  X = The category of the message 
    byte type X   [MSB]  X = The id of the message 
    byte type X   [LSB]  X = The id of the message 

[DATA] = 
    The actuall data for the specified message, 
    this format really differs a lot. 

    It always starts with a DRCode which is one byte. 
    It more or less specifies the general structure of 
    the data, but even within the same structure the data 
    can mean many different things and have different lenghts. 
    (I think this is an artifact of the INCA_XDR tool) 

[TAIL] = 
    byte 0x0D 

正如你可以看到有大量的开销数据,但这是因为 协议需要与两个RS232(点对点,点对多点)和TCP/IP(P2P)的工作。

name  size value 
    drcode  1  1 
    name  8    contains a name that can be used as a file name (only alphanumeric characters allowed) 
    timestamp 14    yyyymmddhhmmss contains timestamp of bitmap library 
    size  4    size of bitmap library to be loaded 
    options  1    currently no options 

或者,它可能有一个完全不同的结构:

name  size value 
    drcode  1  2 
    lastblock 1  0 - 1 1 indicates last block. Firmware can be stored 
    blocknumber 2    Indicates block of firmware 
    blocksize 2  N  size of block to load 
    blockdata N    data of block of firmware 

有时,它只是一个DRCode并没有额外的数据。

基于组和类型字段,模拟器 需要执行某些操作。所以首先我们看看这两个字段,并基于此我们知道对数据 有什么期望,并且必须正确解析它。

然后需要生成响应数据,其中还有许多不同的数据结构。一些消息简单地生成ACK或NACK消息,而另一些则生成数据的真实答复。

我们决定把东西分成小块。

首先是IDataProcessor。

实现此接口的类负责 验证原始数据并生成Message类的实例。 他们不负责通信,他们只是通过一个字节[]

原始数据验证意味着检查标题的校验和,crc和长度错误。

生成的消息被传递给实现IMessageProcessor的类。 即使原始数据被认为是无效的,因为IDataProcessor没有 回应消息的概念或其他任何东西,它所做的只是验证原始数据。

,告知错误IMessageProcessor,一些额外的属性已添加 到Message类:

bool nakError = false; 
bool tailError = false; 
bool crcError = false; 
bool headerError = false; 
bool lengthError = false; 

他们没有相关的协议,只存在了IMessageProcessor

的IMessageProcessor是真正的工作在哪里完成。 因为所有不同的消息组和类型的我决定 使用F#实现IMessageProcessor接口,因为模式匹配 似乎是一个很好的方式,以避免大量的嵌套if/else和种姓语句。我之前没有使用过F#或LINQ和SQL以外的函数式语言的经验)

IMessageProcessor分析数据并决定在IHardwareController上应该调用哪些方法 。这似乎是多余的有IHardwareController, 但我们希望能够有不同的实现 掉出来,而不是将被迫使用F#。目前的实现是一个WPF窗口,但它可能是一个Cocoa#窗口或简单的控制台。

IHardwareController还负责管理状态,因为 开发人员应该能够通过用户界面操纵硬件参数和错误。

因此,一旦IMessageProcessor在IHardwareController上调用了正确的方法, 就必须生成响应消息。再次...这些响应消息 中的数据可以有许多不同的结构。

最终IDataFactory用于将消息转换为原始协议数据 准备发送到任何负责通信的类。 (数据的附加封装可能需要例如)

这是什么“硬”关于编写这些代码,但所有的不同 命令和数据结构需要很多很多的代码,并有少数 事情我们可以重用。 (至少据我现在可以看到的,希望有人能证明我错了)

这是我第一次使用F#,所以我其实学习,我去。下面的代码远没有完成 ,可能看起来像一个巨大的混乱。它只有实现了所有消息的汉福的协议 ,我可以告诉你有很多很多的人。所以这个文件会变得很大!

重要的是知道:字节顺序颠倒通过线路(历史原因)

module Arendee.Hardware.MessageProcessors 

open System; 
open System.Collections 
open Arendee.Hardware.Extenders 
open Arendee.Hardware.Interfaces 
open System.ComponentModel.Composition 
open System.Threading 
open System.Text 

let VPL_NOERROR = (uint16)0 
let VPL_CHECKSUM = (uint16)1 
let VPL_FRAMELENGTH = (uint16)2 
let VPL_OUTOFSEQUENCE = (uint16)3 
let VPL_GROUPNOTSUPPORTED = (uint16)4 
let VPL_REQUESTNOTSUPPORTED = (uint16)5 
let VPL_EXISTS = (uint16)6 
let VPL_INVALID = (uint16)7 
let VPL_TYPERROR = (uint16)8 
let VPL_NOTLOADING = (uint16)9 
let VPL_NOTFOUND = (uint16)10 
let VPL_OUTOFMEM = (uint16)11 
let VPL_INUSE = (uint16)12 
let VPL_SIZE = (uint16)13 
let VPL_BUSY = (uint16)14 
let SYNC_BYTE = (byte)0xE3 
let TAIL_BYTE = (byte)0x0D 
let MESSAGE_GROUP_VERSION = 3uy 
let MESSAGE_GROUP = 701us 


[<Export(typeof<IMessageProcessor>)>] 
type public StandardMessageProcessor() = class 
    let mutable controller : IHardwareController = null    

    interface IMessageProcessor with 
     member this.ProcessMessage m : Message = 
      printfn "%A" controller.Status 
      controller.Status <- ControllerStatusExtender.DisableBit(controller.Status,ControllerStatus.Nak) 

      match m with 
      | m when m.LengthError -> this.nakResponse(m,VPL_FRAMELENGTH) 
      | m when m.CrcError -> this.nakResponse(m,VPL_CHECKSUM) 
      | m when m.HeaderError -> this.nakResponse(m,VPL_CHECKSUM) 
      | m -> this.processValidMessage m 
      | _ -> null  

     member public x.HardwareController 
      with get() = controller 
      and set y = controller <- y     
    end 

    member private this.processValidMessage (m : Message) = 
     match m.Intro.MessageGroup with 
     | 701us -> this.processDefaultGroupMessage(m); 
     | _ -> this.nakResponse(m, VPL_GROUPNOTSUPPORTED); 

    member private this.processDefaultGroupMessage(m : Message) = 
     match m.Intro.MessageType with 
     | (1us) -> this.firmwareVersionListResponse(m)      //ListFirmwareVersions    0 
     | (2us) -> this.StartLoadingFirmwareVersion(m)      //StartLoadingFirmwareVersion  1 
     | (3us) -> this.LoadFirmwareVersionBlock(m)      //LoadFirmwareVersionBlock   2 
     | (4us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //RemoveFirmwareVersion    3 
     | (5us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //ActivateFirmwareVersion   3   
     | (12us) -> this.nakResponse(m,VPL_FRAMELENGTH)      //StartLoadingBitmapLibrary   2 
     | (13us) -> this.nakResponse(m,VPL_FRAMELENGTH)      //LoadBitmapLibraryBlock   2   
     | (21us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //ListFonts       0 
     | (22us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //LoadFont       4 
     | (23us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //RemoveFont      3 
     | (24us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //SetDefaultFont     3   
     | (31us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //ListParameterSets     0 
     | (32us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //LoadParameterSets     4 
     | (33us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //RemoveParameterSet    3 
     | (34us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //ActivateParameterSet    3 
     | (35us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //GetParameterSet     3   
     | (41us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //StartSelfTest      0 
     | (42us) -> this.returnStatus(m)          //GetStatus       0 
     | (43us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //GetStatusDetail     0 
     | (44us) -> this.ResetStatus(m)      //ResetStatus      5 
     | (45us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //SetDateTime      6 
     | (46us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //GetDateTime      0 
     | _ -> this.nakResponse(m, VPL_REQUESTNOTSUPPORTED) 



    (* The various responses follow *) 

    //Generate a NAK response 
    member private this.nakResponse (message : Message , error) = 
     controller.Status <- controller.Status ||| ControllerStatus.Nak 
     let intro = new MessageIntro() 
     intro.MessageGroupVersion <- MESSAGE_GROUP_VERSION 
     intro.Address <- message.Intro.Address 
     intro.SequenceNumber <- this.setHigh(message.Intro.SequenceNumber) 
     intro.MessageGroup <- MESSAGE_GROUP 
     intro.MessageType <- 130us 
     let errorBytes = UShortExtender.ToIntelOrderedByteArray(error) 
     let data = Array.zero_create(5) 
     let x = this.getStatusBytes 
     let y = this.getStatusBytes 
     data.[0] <- 7uy 
     data.[1..2] <- this.getStatusBytes 
     data.[3..4] <- errorBytes  
     let header = this.buildHeader intro data 
     let message = new Message() 
     message.Header <- header 
     message.Intro <- intro 
     message.Tail <- TAIL_BYTE 
     message.Data <- data 
     message 

    //Generate an ACK response 
    member private this.ackResponse (message : Message) = 
     let intro = new MessageIntro() 
     intro.MessageGroupVersion <- MESSAGE_GROUP_VERSION 
     intro.Address <- message.Intro.Address 
     intro.SequenceNumber <- this.setHigh(message.Intro.SequenceNumber) 
     intro.MessageGroup <- MESSAGE_GROUP 
     intro.MessageType <- 129us 
     let data = Array.zero_create(3); 
     data.[0] <- 0x05uy 
     data.[1..2] <- this.getStatusBytes 
     let header = this.buildHeader intro data 
     message.Header <- header 
     message.Intro <- intro 
     message.Tail <- TAIL_BYTE 
     message.Data <- data 
     message   

    //Generate a ReturnFirmwareVersionList 
    member private this.firmwareVersionListResponse (message : Message) = 
     //Validation 
     if message.Data.[0] <> 0x00uy then 
      this.nakResponse(message,VPL_INVALID) 
     else 
      let intro = new MessageIntro() 
      intro.MessageGroupVersion <- MESSAGE_GROUP_VERSION 
      intro.Address <- message.Intro.Address 
      intro.SequenceNumber <- this.setHigh(message.Intro.SequenceNumber) 
      intro.MessageGroup <- MESSAGE_GROUP 
      intro.MessageType <- 132us  
      let firmwareVersions = controller.ReturnFirmwareVersionList(); 
      let firmwareVersionBytes = BitConverter.GetBytes((uint16)firmwareVersions.Count) |> Array.rev 

      //Create the data 
      let data = Array.zero_create(3 + (int)firmwareVersions.Count * 27) 
      data.[0] <- 0x09uy        //drcode 
      data.[1..2] <- firmwareVersionBytes    //Number of firmware versions 

      let mutable index = 0 
      let loops = firmwareVersions.Count - 1 
      for i = 0 to loops do 
       let nameBytes = ASCIIEncoding.ASCII.GetBytes(firmwareVersions.[i].Name) |> Array.rev 
       let timestampBytes = this.getTimeStampBytes firmwareVersions.[i].Timestamp |> Array.rev 
       let sizeBytes = BitConverter.GetBytes(firmwareVersions.[i].Size) |> Array.rev 

       data.[index + 3 .. index + 10] <- nameBytes 
       data.[index + 11 .. index + 24] <- timestampBytes 
       data.[index + 25 .. index + 28] <- sizeBytes 
       data.[index + 29] <- firmwareVersions.[i].Status 
       index <- index + 27    

      let header = this.buildHeader intro data 
      message.Header <- header 
      message.Intro <- intro 
      message.Data <- data 
      message.Tail <- TAIL_BYTE 
      message 

    //Generate ReturnStatus 
    member private this.returnStatus (message : Message) = 
     //Validation 
     if message.Data.[0] <> 0x00uy then 
      this.nakResponse(message,VPL_INVALID) 
     else 
      let intro = new MessageIntro() 
      intro.MessageGroupVersion <- MESSAGE_GROUP_VERSION 
      intro.Address <- message.Intro.Address 
      intro.SequenceNumber <- this.setHigh(message.Intro.SequenceNumber) 
      intro.MessageGroup <- MESSAGE_GROUP 
      intro.MessageType <- 131us 

      let statusDetails = controller.ReturnStatus(); 

      let sizeBytes = BitConverter.GetBytes((uint16)statusDetails.Length) |> Array.rev 

      let detailBytes = ASCIIEncoding.ASCII.GetBytes(statusDetails) |> Array.rev 

      let data = Array.zero_create(statusDetails.Length + 5) 
      data.[0] <- 0x08uy 
      data.[1..2] <- this.getStatusBytes 
      data.[3..4] <- sizeBytes //Details size 
      data.[5..5 + statusDetails.Length - 1] <- detailBytes 

      let header = this.buildHeader intro data 
      message.Header <- header 
      message.Intro <- intro 
      message.Data <- data 
      message.Tail <- TAIL_BYTE 
      message 

    //Reset some status bytes  
    member private this.ResetStatus (message : Message) = 
     if message.Data.[0] <> 0x05uy then 
      this.nakResponse(message, VPL_INVALID) 
     else   
      let flagBytes = message.Data.[1..2] |> Array.rev 
      let flags = Enum.ToObject(typeof<ControllerStatus>,BitConverter.ToInt16(flagBytes,0)) :?> ControllerStatus 
      let retVal = controller.ResetStatus flags 

      if retVal <> 0x00us then 
       this.nakResponse(message,retVal) 
      else 
       this.ackResponse(message) 

    //StartLoadingFirmwareVersion (Ack/Nak) 
    member private this.StartLoadingFirmwareVersion (message : Message) = 
     if (message.Data.[0] <> 0x01uy) then 
      this.nakResponse(message, VPL_INVALID) 
     else 
      //Analyze the data 
      let name = message.Data.[1..8] |> Array.rev |> ASCIIEncoding.ASCII.GetString 
      let text = message.Data.[9..22] |> Array.rev |> Seq.map(fun x -> ASCIIEncoding.ASCII.GetBytes(x.ToString()).[0]) |> Seq.to_array |> ASCIIEncoding.ASCII.GetString 
      let timestamp = DateTime.ParseExact(text,"yyyyMMddHHmmss",Thread.CurrentThread.CurrentCulture) 

      let size = BitConverter.ToUInt32(message.Data.[23..26] |> Array.rev,0) 
      let overwrite = 
       match message.Data.[27] with 
       | 0x00uy -> false 
       | _ -> true 

      //Create a FirmwareVersion instance 
      let firmware = new FirmwareVersion(); 
      firmware.Name <- name 
      firmware.Timestamp <- timestamp 
      firmware.Size <- size 

      let retVal = controller.StartLoadingFirmwareVersion(firmware,overwrite) 

      if retVal <> 0x00us then 
       this.nakResponse(message, retVal) //The controller denied the request 
      else 
       this.ackResponse(message); 

    //LoadFirmwareVersionBlock (ACK/NAK) 
    member private this.LoadFirmwareVersionBlock (message : Message) = 
     if message.Data.[0] <> 0x02uy then 
      this.nakResponse(message, VPL_INVALID) 
     else 
      //Analyze the data 
      let lastBlock = 
       match message.Data.[1] with 
       | 0x00uy -> false 
       | _true -> true 

      let blockNumber = BitConverter.ToUInt16(message.Data.[2..3] |> Array.rev,0)    
      let blockSize = BitConverter.ToUInt16(message.Data.[4..5] |> Array.rev,0) 
      let blockData = message.Data.[6..6 + (int)blockSize - 1] |> Array.rev 

      let retVal = controller.LoadFirmwareVersionBlock(lastBlock, blockNumber, blockSize, blockData) 

      if retVal <> 0x00us then 
       this.nakResponse(message, retVal) 
      else 
       this.ackResponse(message) 


    (* Helper methods *) 
    //We need to convert the DateTime instance to a byte[] understood by the device "yyyymmddhhmmss" 
    member private this.getTimeStampBytes (date : DateTime) = 
     let stringNumberToByte s = Byte.Parse(s.ToString()) //Casting to (byte) would give different results 

     let yearString = date.Year.ToString("0000") 
     let monthString = date.Month.ToString("00") 
     let dayString = date.Day.ToString("00") 
     let hourString = date.Hour.ToString("00") 
     let minuteString = date.Minute.ToString("00") 
     let secondsString = date.Second.ToString("00") 

     let y1 = stringNumberToByte yearString.[0] 
     let y2 = stringNumberToByte yearString.[1] 
     let y3 = stringNumberToByte yearString.[2] 
     let y4 = stringNumberToByte yearString.[3] 
     let m1 = stringNumberToByte monthString.[0] 
     let m2 = stringNumberToByte monthString.[1] 
     let d1 = stringNumberToByte dayString.[0] 
     let d2 = stringNumberToByte dayString.[1] 
     let h1 = stringNumberToByte hourString.[0] 
     let h2 = stringNumberToByte hourString.[1] 
     let min1 = stringNumberToByte minuteString.[0] 
     let min2 = stringNumberToByte minuteString.[1] 
     let s1 = stringNumberToByte secondsString.[0] 
     let s2 = stringNumberToByte secondsString.[1] 

     [| y1 ; y2 ; y3 ; y4 ; m1 ; m2 ; d1 ; d2 ; h1 ; h2 ; min1 ; min2 ; s1; s2 |] 

    //Sets the high bit of a byte to 1 
    member private this.setHigh (b : byte) : byte = 
     let array = new BitArray([| b |]) 
     array.[7] <- true 
     let mutable converted = [| 0 |] 
     array.CopyTo(converted, 0); 
     (byte)converted.[0] 

    //Build the header of a Message based on Intro + Data 
    member private this.buildHeader (intro : MessageIntro) (data : byte[]) = 
     let headerLength = 7; 
     let introLength = 7; 
     let length = (uint16)(headerLength + introLength + data.Length) 
     let crcData = ByteArrayExtender.Concat(intro.GetRawData(),data) 
     let crcValue = ByteArrayExtender.CalculateCRC16(crcData) 
     let lengthBytes = UShortExtender.ToIntelOrderedByteArray(length); 
     let crcValueBytes = UShortExtender.ToIntelOrderedByteArray(crcValue); 
     let headerChecksum = (byte)(SYNC_BYTE ^^^ lengthBytes.[0] ^^^ lengthBytes.[1] ^^^ crcValueBytes.[0] ^^^ crcValueBytes.[1]) 
     let header = new MessageHeader(); 
     header.Sync <- SYNC_BYTE 
     header.Length <- length 
     header.HeaderChecksum <- headerChecksum 
     header.DataChecksum <- crcValue 
     header 

    member private this.getStatusBytes = 
     let l = controller.Status 
     let status = (uint16)controller.Status 
     let statusBytes = BitConverter.GetBytes(status); 
     statusBytes |> Array.rev 

end 

(请注意,真正的源头,这些类有不同的名称,而不是“硬件”更具体)

我希望的建议,如何改善代码,甚至不同的方式来处理这个问题。 例如,将使用动态语言 - 如IronPython使事情更容易, 我是在错误的方式去一起。你有什么经验,像这样的问题, 你会改变什么,避免,等等......

更新:

基于由Brian答案,我写下了以下内容:

type DrCode9Item = {Name : string ; Timestamp : DateTime ; Size : uint32; Status : byte} 
type DrCode11Item = {Id : byte ; X : uint16 ; Y : uint16 ; SizeX : uint16 ; SizeY : uint16 
        Font : string ; Alignment : byte ; Scroll : byte ; Flash : byte} 
type DrCode12Item = {Id : byte ; X : uint16 ; Y : uint16 ; SizeX : uint16 ; SizeY : uint16} 
type DrCode14Item = {X : byte ; Y : byte} 

type DRType = 
| DrCode0 of byte 
| DrCode1 of byte * string * DateTime * uint32 * byte 
| DrCode2 of byte * byte * uint16 * uint16 * array<byte> 
| DrCode3 of byte * string 
| DrCode4 of byte * string * DateTime * byte * uint16 * array<byte> 
| DrCode5 of byte * uint16 
| DrCode6 of byte * DateTime 
| DrCode7 of byte * uint16 * uint16 
| DrCode8 of byte * uint16 * uint16 * uint16 * array<byte> 
| DrCode9 of byte * uint16 * array<DrCode9Item> 
| DrCode10 of byte * string * DateTime * uint32 * byte * array<byte> 
| DrCode11 of byte * array<DrCode11Item> 
| DrCode12 of byte * array<DrCode12Item> 
| DrCode13 of byte * uint16 * byte * uint16 * uint16 * string * byte * byte 
| DrCode14 of byte * array<DrCode14Item> 

我可以继续做这一切的DR类型(相当多的), 但我仍然不明白这对我有什么帮助。我已经在Wikibooks和F#的基础上阅读了关于它的 ,但是还没有点击我的头像。

更新2

所以,我知道我能做到以下几点:

let execute dr = 
    match dr with 
    | DrCode0(drCode) -> printfn "Do something" 
    | DrCode1(drCode, name, timestamp, size, options) -> printfn "Show the size %A" size 
    | _ ->() 
let date = DateTime.Now 

let x = DrCode1(1uy,"blabla", date, 100ul, 0uy) 

但是当消息进入的IMessageProcessor, 的的choise由右边有什么样的信息呢是 然后调用正确的功能。以上只是 是额外的代码,至少这是如何理解它, 所以我必须真的在这里忽略了点...但我没有看到它。

execute x 

回答

1

我认为F#很自然地适合通过区分联合表示这个域中的消息;我在想像

type Message = 
    | Message1 of string * DateTime * int * byte //name,timestamp,size,options 
    | Message2 of bool * short * short * byte[] //last,blocknum,blocksize,data 
    ... 

以及解析/解析来自/去往字节数组的消息的方法。正如你所说,这项工作很简单,只是单调乏味。

我对消息的处理不太清楚,但总体上根据您的描述,听起来像您有处理它。

我有点担心你的'工具灵活性' - 你的约束是什么? (例如.Net,必须由熟悉X,Y,Z技术的程序员来维护......)

+0

关于约束条件: 我们希望模拟器的核心是跨平台的。 我们从不使用本机代码,特别是不使用C代码。 我已经看到C硬件开发人员编写代码的方式,这太可怕了!无处不在的缩略词 第三方使用他们想要与模拟器通信的任何内容(通过tcp/ip或rs232)。 – TimothyP 2009-05-04 07:58:33

1

这是我的2美分(警告:我知道没有F#):你有一个精细指定的输入文件,即使具有完整的语法,也可以使用 。您想要将文件的内容映射到操作。因此,我建议你解析这个文件。 F#是一种功能语言,它可能适合称为Recursive Descent Parsing的解析技术。 The book "Expert F#"包含递归下降解析的讨论。