The bitfield tracks which pieces of the transfer are already downloaded, and which aren’t. We therefore only need a structure which can track two states per value, which is why we use a bitarray library. In fact, we use only a subset of these functions, and rename a few to make them more expressive.

We also need to be able to translate the bitfield from/to the network where the high bit (right-most in network order) in the first byte corresponds to piece index 0.

Signature

namespace BitTorrent

type Bitfield = Common.Bitarray

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Bitfield =

  type t = Bitfield

  ///[create n] creates a new Bitfield representing [n] pieces.
  val create : numPieces:int -> t

  ///[length bitfield] returns the number of pieces handled by [bitfield].
  val length : bitfield:t -> int

  ///[put nth flag bitfield] sets the [nth] value of the [bitfield] to [flag].
  val put : n:int -> flag:bool -> bitfield:t -> unit

  ///[set nth bitfield] sets the [nth] value of the [bitfield] to [true].
  val set : n:int -> bitfield:t -> unit

  ///[unset nth bitfield] sets the [nth] value of the [bitfield] to [false].
  val unset : n:int -> bitfield:t -> unit

  val iteri : f:(int -> bool -> unit) -> bitfield:t -> unit

  ///[verifiedPieces bitfield] returns the indexes of the pieces which are set to true.
  val verifiedPieces : bitfield:t -> list<int>

  ///[unverifiedPieces bitfield] returns the indexes of the pieces which are set to false.
  val unverifiedPieces : bitfield:t -> list<int>

  ///[hasPiece nth bitfield] returns true if the [nth] piece is set to true.
  val hasPiece : n:int -> bitfield:t -> bool

  ///[isVirgin bitfield] returns true if no bit is set in the [bitfield].
  val isVirgin : bitfield:t -> bool

  ///[isSeeder bitfield] returns true if all bits are set in the [bitfield].
  val isSeeder : bitfield:t -> bool

  ///[( |Virgin|Leecher|Seeder| ) bitfield] returns Virgin if no piece is set to true,
  ///Seeder if they are all set to true, Leecher otherwise.
  val ( |Virgin|Leecher|Seeder| ) : bitfield:t -> Choice<unit,unit,unit>

  ///[toBytes bitfield] converts [bitfield] to a byte array ready to be sent over the network.
  ///The high bit in the first byte corresponds to piece index 0.
  ///Spare bits at the end are set to zero.
  val toBytes : bitfield:t -> byte[]

  ///[fromBytes bytes numPieces] converts [bytes] to a Bitfield. The high bit in the first byte
  ///corresponds to piece index 0. Spare bits at the end are set to zero.
  ///An exception is raised if [numPieces] is inconsistent with the number of bits encoded
  ///in [bytes].
  val fromBytes : bytes:byte[] -> numPieces:int -> t
  

Implementation

namespace BitTorrent

type Bitfield = Common.Bitarray

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Bitfield =

  open Common

  type t = Bitfield

  let create n = Bitarray.create n

  let length bitfield = Bitarray.length bitfield

  let put i flag bitfield = Bitarray.put i flag bitfield

  let set i bitfield = Bitarray.set i bitfield

  let unset i bitfield = Bitarray.unset i bitfield

  let iteri f bitfield = Bitarray.iteri f bitfield

  let verifiedPieces bitfield = Bitarray.setBits bitfield

  let unverifiedPieces bitfield = Bitarray.unsetBits bitfield

  let hasPiece piece bitfield = Bitarray.isSet piece bitfield

  let isVirgin bitfield = Bitarray.areAllUnset bitfield

  let isSeeder bitfield = Bitarray.areAllSet bitfield

  let ( |Virgin|Leecher|Seeder| ) bitfield =
    if isVirgin bitfield then Virgin
    elif isSeeder bitfield then Seeder
    else Leecher

  //-----------------------------------------------------------
  // Convert the byte array representation of a bitfield between the
  // network-usable version and a bitarray-usable version.
  //-----------------------------------------------------------  

  //The bitfield message is variable length, where X is the length of the bitfield.
  //The payload is a bitfield representing the pieces that have been successfully downloaded.
  //The high bit (right-most in network order) in the first byte corresponds to piece index 0.
  //Bits that are cleared indicated a missing piece, and set bits indicate a valid and available piece.
  //Spare bits at the end are set to zero

  let isSetBit bits bit =
    bits &&& byte (1 <<< bit) <> 0uy

  let setBit bits bit =
    bits ||| byte (1 <<< (7 - bit))

  let toggleBits (data:byte[]) =
    let nBytes = Array.length data
    let convertedBytes = Array.create nBytes 0uy
    for currentByte in 0 .. nBytes - 1 do
      for currentBit in 0 .. 7 do
        if isSetBit data.[currentByte] currentBit then
          convertedBytes.[currentByte] <- setBit convertedBytes.[currentByte] currentBit
    convertedBytes 

  let toBytes bitfield =
    bitfield |> Bitarray.toBytes |> toggleBits

  let fromBytes data numBits =
    Bitarray.fromBytes (toggleBits data) numBits

Example

let test x =
  printfn "bitfield length : %d" (Bitfield.length x)
  printfn "bitfield has piece 0 : %A" (Bitfield.hasPiece 0 x)
  printfn "bitfield verified pieces: %A" (Bitfield.verifiedPieces x)
  printfn "bitfield unverified pieces: %A" (Bitfield.unverifiedPieces x)
  printfn "bitfield is virgin: %A" (Bitfield.isVirgin x)
  printfn "bitfield is seeder: %A" (Bitfield.isSeeder x)
  printfn "bitfield contais only [false] flags : %A" (Bitarray.areAllUnset x)
  printfn "bitfield contais only [true] flags : %A" (Bitarray.areAllSet x)
  printfn "bitfield to bytes : %A" (Bitfield.toBytes x)
  printfn "bitfield from bytes : %A" (Bitfield.fromBytes (Bitfield.toBytes x) (Bitfield.length x))
  printfn "================="

//no bit is set, thus the client is a virgin
let x = Bitfield.create 10
test x ;;

Bitfield.set 0 x
test x ;;

Bitfield.unset 0 x
test x ;;

//set all bits to true, i.e. when the client is seeding
x |> Bitfield.iteri (fun i e -> Bitfield.set i x)
test x;;

Comments are closed.