packages feed

capnp-0.17.0.0: lib/Capnp/Address.hs

{-# LANGUAGE RecordWildCards #-}

-- |
-- Module: Capnp.Address
-- Description: Utilities for manipulating addresses within capnproto messages.
--
-- This module provides facilities for manipulating raw addresses within
-- Cap'N Proto messages.
--
-- This is a low level module that very few users will need to use directly.
module Capnp.Address
  ( WordAddr (..),
    CapAddr (..),
    Addr (..),
    OffsetError (..),
    computeOffset,
    pointerFrom,
    resolveOffset,
  )
where

import Capnp.Bits (WordCount)
import qualified Capnp.Pointer as P
import Data.Bits
import Data.Int
import Data.Word

-- | The address of a word within a message
data WordAddr = WordAt
  { -- | Segment number
    segIndex :: !Int,
    -- | offset in words from the start of the segment.
    wordIndex :: !WordCount
  }
  deriving (Show, Eq)

-- | The "address" of a capability
newtype CapAddr = Cap Word32 deriving (Show, Eq)

-- | An address, i.e. a location that a pointer may point at.
data Addr
  = -- | The address of some data in the message.
    WordAddr !WordAddr
  | -- | The "address" of a capability.
    CapAddr !CapAddr
  deriving (Show, Eq)

-- | An error returned by 'computeOffset'; this describes the reason why a
-- value cannot be directly addressed from a given location.
data OffsetError
  = -- | The pointer and the value are in different segments.
    DifferentSegments
  | -- | The pointer is in the correct segment, but too far away to encode the
    -- offset. (more than 30 bits would be required). This can only happen with
    -- segments that are > 8 GiB, which this library refuses to either decode
    -- or generate, so this should not come up in practice.
    OutOfRange

-- | @'computeOffset' ptrAddr valueAddr@ computes the offset that should be
-- stored in a struct or list pointer located at @ptrAddr@, in order to point
-- at a value located at @valueAddr@. If the value cannot be directly addressed
-- by a pointer at @ptrAddr@, then this returns 'Left', with the 'OffsetError'
-- describing the problem.
computeOffset :: WordAddr -> WordAddr -> Either OffsetError WordCount
computeOffset ptrAddr valueAddr
  | segIndex ptrAddr /= segIndex valueAddr = Left DifferentSegments
  | otherwise =
      let offset = wordIndex valueAddr - (wordIndex ptrAddr + 1)
       in if offset >= 1 `shiftL` 30
            then Left OutOfRange
            else Right offset

-- | @'pointerFrom' ptrAddr targetAddr ptr@ updates @ptr@, such that it is
-- correct to target a value located at @targetAddr@ given that the pointer
-- itself is located at @ptrAddr@. Returns 'Left' if this is not possible.
--
-- It is illegal to call this on a capability pointer.
--
-- For far pointers, @targetAddr@ is taken to be the address of the landing pad,
-- rather than the final value.
pointerFrom :: WordAddr -> WordAddr -> P.Ptr -> Either OffsetError P.Ptr
pointerFrom _ _ (P.CapPtr _) = error "pointerFrom called on a capability pointer."
pointerFrom _ WordAt {..} (P.FarPtr twoWords _ _) =
  Right $ P.FarPtr twoWords (fromIntegral wordIndex) (fromIntegral segIndex)
pointerFrom ptrAddr targetAddr (P.StructPtr _ dataSz ptrSz) =
  flip fmap (computeOffset ptrAddr targetAddr) $
    \off -> P.StructPtr (fromIntegral off) dataSz ptrSz
pointerFrom ptrAddr targetAddr (P.ListPtr _ eltSpec) =
  flip fmap (computeOffset ptrAddr targetAddr) $
    \off -> P.ListPtr (fromIntegral off) eltSpec

-- | Add an offset to a WordAddr.
resolveOffset :: WordAddr -> Int32 -> WordAddr
resolveOffset addr@WordAt {..} off =
  addr {wordIndex = wordIndex + fromIntegral off + 1}