user1158550
user1158550

Reputation:

F# records, usage, code clarity

Background:

I find myself harnessing F# Records a lot. Currently I am working on a project for packet dissection & replay of a proprietary binary protocol (a protocol that is very strangely designed ...).

We define the skeleton record for the packet.

type bytes = byte array
type packetSkeleton = {
    part1 : bytes
    part2 : bytes 
    ... }

Now, it is easy to use this to 'dissect' our packet, (really just giving names to the byte fields).

let dissect (raw : bytes) =
  let slice a b = raw.[a..b]
  { part1 = slice 0 4
    part2 = slice 4 5
    ... }

This works perfectly even for longish packets, we can even use some neat recursive functions if there is a predicable pattern to the slicing.

So I dissect the packet, pull out the fields that I need and create a packet based off the packetSkeleton using the fields I took from the dissection, which by now is starting to look a bit awkward:

let createAuthStub a b c d e f g h ... =
   { part1 = a; part2 = b
     part3 = d; ...
   }

Then, after creating the populated stub, I need to deserialise it to a form that can be put on the wire:

(* packetSkeleton -> byte array *)
let deserialise (packet : packetSkeleton) =
  [| packet.part1; packet.part2; ... |]

let xab = dissect buf
let authStub = createAuthStub xab.part1 1 2 xab.part9 ...

deserialise authStub |> send

So it ends up that I have 3 areas, the record type, the creation of the record for a given packet, and the deserialised byte array. Something tells me that this is a poor design choice on my part in terms of code clarity, and I can already feel it starting to shoot me in the foot even at this early stage.

Questions:

a) Am I using the correct datatype for such a project? Is my approach correct?
b) Should I just give up on trying to make this code feel clean?

As I am kinda coding this by touch and go, I would appreciate some insights!

P.S I realise that this problem is quite suited for C, but F# is more fun (additionally verification of the dissector later on sounds appealing)!

Upvotes: 2

Views: 232

Answers (2)

Daniel
Daniel

Reputation: 47914

If a packet could be rather large packetSkeleton might grow unwieldy. Another option is to work with the raw bytes and define a module that reads/writes each part.

module Packet
  let Length = 42
  let GetPart1 src = src.[0..3]
  let SetPart1 src dst = Array.blit src 0 dst 0 4
  let GetPart2 src = src.[4..5]
  let SetPart2 src dst = Array.blit src 0 dst 4 2
  ...

open Packet 

let createAuthStub bytes b c =
  let resp = Array.zeroCreate Packet.Length
  SetPart1 (GetPart1 bytes) 
  SetPart2 b resp
  SetPart3 c resp
  SetPart4 (GetPart9 bytes) 
  resp

This removes the need for de/serialization functions (and probably helps performance a bit).

EDIT

Creating a wrapper type is another option

type Packet(bytes: byte[]) =
  new() = Packet(Array.zeroCreate Packet.Length)
  static member Length = 42
  member x.Part1
    with get() = bytes.[0..3]
    and set value = Array.blit value 0 bytes 0 4
    ...

which might reduce code a bit:

let createAuthStub (req: Packet) b c =
  let resp = Packet()
  resp.Part1 <- req.Part1
  resp.Part2 <- b
  resp.Part3 <- c
  resp.Part4 <- req.Part9
  resp

Upvotes: 2

Tomas Petricek
Tomas Petricek

Reputation: 243096

I think your approach is essentially sound - but of course, it is difficult to tell without knowing more details.

I think one key idea that shows in your code and that is key to functional architecture is the separation between types (used to model the problem domain) and the processing functionality that creates the values of the domain model, processes it and formats them.

In your case:

  • The types bytes and packetSkeleton model the problem domain
  • The function createAuthStub processes your domain (and I agree with Daniel that it might be more readable if it took the whole packetSkeleton as an argument)
  • The function deserialize turns your domain back to bytes

I think this way of structuring code is quite good, because it separates different concerns of the program. I even wrote an article that tries to describe this as a more general programming approach.

Upvotes: 1

Related Questions