-- | A bundle's program inside a Mach-O executable (macOS), in the section
-- @__UNISON,__uc@ (see "Unison.Runtime.Bundle").
--
-- A Mach-O executable is a header, a list of load commands, and the bytes
-- of its segments, in order. Each segment's load command says where its
-- bytes are in the file (@fileoff@, @filesize@) and in memory (@vmaddr@,
-- @vmsize@), and lists its sections. The last segment is always
-- @__LINKEDIT@: the symbol table, the dynamic loader's information and the
-- code signature, which other load commands point into by file offset.
--
-- unisonrt is linked with @-sectcreate __UNISON __uc@, so that the segment
-- @__UNISON@, holding that one section, is the segment right before
-- @__LINKEDIT@, in the file and in memory. That makes the section the one
-- place that can grow without moving any code or data: growing it moves
-- only @__LINKEDIT@, which no code refers to by address, and the load
-- commands say where @__LINKEDIT@ and everything in it now is. Since the
-- program is then part of the image, the code signature covers it, once
-- the executable is signed again.
--
-- Field offsets are from @<mach-o/loader.h>@.
module Unison.Runtime.Bundle.MachO
  ( isMachO,
    programSection,
    setProgramSection,
  )
where

import Control.Monad (unless, when)
import Data.Bits (shiftL, shiftR, (.&.))
import Data.ByteString qualified as B
import Data.List (find, sortOn)
import Data.Word (Word32, Word64)

-- | Whether these are the bytes of a 64-bit little-endian Mach-O file
-- (arm64 and x86-64 macOS)
isMachO :: B.ByteString -> Bool
isMachO :: ByteString -> Bool
isMachO ByteString
bytes = ByteString -> Int
B.length ByteString
bytes Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
headerSize Bool -> Bool -> Bool
&& ByteString -> Int -> Word32
u32 ByteString
bytes Int
0 Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
== Word32
mhMagic64

-- | The contents of the section @__UNISON,__uc@, if the file has it
programSection :: B.ByteString -> Maybe B.ByteString
programSection :: ByteString -> Maybe ByteString
programSection ByteString
bytes = do
  (Segment
_, Int
sect) <- ByteString -> Maybe (Segment, Int)
findSection ByteString
bytes
  let off :: Int
off = Word32 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int -> Word32
u32 ByteString
bytes (Int
sect Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
sectionOffset))
      size :: Int
size = Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int -> Word64
u64 ByteString
bytes (Int
sect Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
sectionSize))
  ByteString -> Maybe ByteString
forall a. a -> Maybe a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Int -> ByteString -> ByteString
B.take Int
size (Int -> ByteString -> ByteString
B.drop Int
off ByteString
bytes))

-- | The file with the section @__UNISON,__uc@ holding these contents
-- instead. Its code signature no longer matches; the caller signs it
-- again. The steps:
--
-- 1. Find the segment @__UNISON@ and its section, and check that the
--    segment is the one right before @__LINKEDIT@, in the file and in
--    memory.
-- 2. The segment's new size: from its start to the end of the new
--    contents, in whole pages, since segments are mapped by page.
-- 3. @__LINKEDIT@ moves to just after it, in the file and in memory, by
--    the change in that size.
-- 4. The load commands are patched: the segment's sizes and the section's,
--    @__LINKEDIT@'s place, and every file offset another load command holds
--    into @__LINKEDIT@ ('linkEditOffsetFields').
-- 5. The new file is the bytes before the segment (with the patched load
--    commands, which are among them), the segment's new bytes, and
--    @__LINKEDIT@'s bytes, unchanged.
setProgramSection :: B.ByteString -> B.ByteString -> Either String B.ByteString
setProgramSection :: ByteString -> ByteString -> Either String ByteString
setProgramSection ByteString
bytes ByteString
contents = do
  -- 1.
  Bool -> Either String () -> Either String ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (ByteString -> Bool
isMachO ByteString
bytes) (String -> Either String ()
forall a b. a -> Either a b
Left String
"not a 64-bit Mach-O executable")
  (Segment
uni, Int
sect) <- Either String (Segment, Int)
-> ((Segment, Int) -> Either String (Segment, Int))
-> Maybe (Segment, Int)
-> Either String (Segment, Int)
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String -> Either String (Segment, Int)
forall a b. a -> Either a b
Left String
"no __UNISON,__uc section: this unisonrt was linked without one") (Segment, Int) -> Either String (Segment, Int)
forall a b. b -> Either a b
Right (ByteString -> Maybe (Segment, Int)
findSection ByteString
bytes)
  Segment
linkEdit <- Either String Segment
-> (Segment -> Either String Segment)
-> Maybe Segment
-> Either String Segment
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String -> Either String Segment
forall a b. a -> Either a b
Left String
"no __LINKEDIT segment") Segment -> Either String Segment
forall a b. b -> Either a b
Right (ByteString -> ByteString -> Maybe Segment
findSegment ByteString
"__LINKEDIT" ByteString
bytes)
  let inFile :: [Segment]
inFile = (Segment -> Word64) -> [Segment] -> [Segment]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn Segment -> Word64
segFileOff [Segment
s | Segment
s <- ByteString -> [Segment]
segments ByteString
bytes, Segment -> Word64
segFileSize Segment
s Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
> Word64
0]
  Bool -> Either String () -> Either String ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((Segment -> ByteString) -> [Segment] -> [ByteString]
forall a b. (a -> b) -> [a] -> [b]
map Segment -> ByteString
segName (Int -> [Segment] -> [Segment]
forall a. Int -> [a] -> [a]
drop ([Segment] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Segment]
inFile Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
2) [Segment]
inFile) [ByteString] -> [ByteString] -> Bool
forall a. Eq a => a -> a -> Bool
/= [ByteString
"__UNISON", ByteString
"__LINKEDIT"]) (Either String () -> Either String ())
-> Either String () -> Either String ()
forall a b. (a -> b) -> a -> b
$
    String -> Either String ()
forall a b. a -> Either a b
Left String
"the __UNISON segment is not the one before __LINKEDIT in the file"
  Bool -> Either String () -> Either String ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((Segment -> Bool) -> [Segment] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\Segment
s -> Segment -> ByteString
segName Segment
s ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
/= ByteString
"__LINKEDIT" Bool -> Bool -> Bool
&& Segment -> Word64
segVmAddr Segment
s Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
> Segment -> Word64
segVmAddr Segment
uni) (ByteString -> [Segment]
segments ByteString
bytes)) (Either String () -> Either String ())
-> Either String () -> Either String ()
forall a b. (a -> b) -> a -> b
$
    String -> Either String ()
forall a b. a -> Either a b
Left String
"the __UNISON segment is not the one before __LINKEDIT in memory"
  let -- 2. the section's offset within its segment, then the segment's size
      sectStart :: Word64
sectStart = Word32 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int -> Word32
u32 ByteString
bytes (Int
sect Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
sectionOffset)) Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
- Segment -> Word64
segFileOff Segment
uni
      newSize :: Word64
newSize = Word64 -> Word64
roundUpToPage (Word64
sectStart Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Int -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int
B.length ByteString
contents))
      -- 3.
      newLinkEditOff :: Word64
newLinkEditOff = Segment -> Word64
segFileOff Segment
uni Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Word64
newSize
      newLinkEditAddr :: Word64
newLinkEditAddr = Segment -> Word64
segVmAddr Segment
uni Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Word64
newSize
      moved :: Integer
moved = Word64 -> Integer
forall a. Integral a => a -> Integer
toInteger Word64
newLinkEditOff Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Word64 -> Integer
forall a. Integral a => a -> Integer
toInteger (Segment -> Word64
segFileOff Segment
linkEdit)
      -- 4.
      patches :: [ByteString -> ByteString]
patches =
        [ Int -> Word64 -> ByteString -> ByteString
put64 (Segment -> Int
segCmd Segment
uni Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentVmSize) Word64
newSize,
          Int -> Word64 -> ByteString -> ByteString
put64 (Segment -> Int
segCmd Segment
uni Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentFileSize) Word64
newSize,
          Int -> Word64 -> ByteString -> ByteString
put64 (Int
sect Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
sectionSize) (Int -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int
B.length ByteString
contents)),
          Int -> Word64 -> ByteString -> ByteString
put64 (Segment -> Int
segCmd Segment
linkEdit Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentVmAddr) Word64
newLinkEditAddr,
          Int -> Word64 -> ByteString -> ByteString
put64 (Segment -> Int
segCmd Segment
linkEdit Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentFileOff) Word64
newLinkEditOff
        ]
          [ByteString -> ByteString]
-> [ByteString -> ByteString] -> [ByteString -> ByteString]
forall a. [a] -> [a] -> [a]
++ [ Int -> Word32 -> ByteString -> ByteString
put32 Int
field (Integer -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word32 -> Integer
forall a. Integral a => a -> Integer
toInteger Word32
old Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
moved))
             | (Int
at, Word32
cmd) <- ByteString -> [(Int, Word32)]
loadCommands ByteString
bytes,
               Int
field <- (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+) (Int -> Int) -> [Int] -> [Int]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Word32 -> [Int]
linkEditOffsetFields Word32
cmd,
               -- zero means the command has no such table
               let old :: Word32
old = ByteString -> Int -> Word32
u32 ByteString
bytes Int
field,
               Word32
old Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
/= Word32
0
             ]
      before :: ByteString
before = (ByteString -> (ByteString -> ByteString) -> ByteString)
-> ByteString -> [ByteString -> ByteString] -> ByteString
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (\ByteString
bs ByteString -> ByteString
patch -> ByteString -> ByteString
patch ByteString
bs) (Int -> ByteString -> ByteString
B.take (Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Segment -> Word64
segFileOff Segment
uni)) ByteString
bytes) [ByteString -> ByteString]
patches
      -- 5.
      segment :: ByteString
segment =
        Int -> ByteString -> ByteString
B.take (Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
sectStart) (Int -> ByteString -> ByteString
B.drop (Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Segment -> Word64
segFileOff Segment
uni)) ByteString
bytes)
          ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
contents
          ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Int -> Word8 -> ByteString
B.replicate (Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
newSize Int -> Int -> Int
forall a. Num a => a -> a -> a
- Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
sectStart Int -> Int -> Int
forall a. Num a => a -> a -> a
- ByteString -> Int
B.length ByteString
contents) Word8
0
  pure (ByteString
before ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
segment ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Int -> ByteString -> ByteString
B.drop (Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Segment -> Word64
segFileOff Segment
linkEdit)) ByteString
bytes)

-- | The offsets, within a load command of this type, of its fields that
-- hold a file offset into @__LINKEDIT@
linkEditOffsetFields :: Word32 -> [Int]
linkEditOffsetFields :: Word32 -> [Int]
linkEditOffsetFields Word32
cmd
  -- symoff, stroff
  | Word32
cmd Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
== Word32
lcSymtab = [Int
8, Int
16]
  -- tocoff, modtaboff, extrefsymoff, indirectsymoff, extreloff, locreloff
  | Word32
cmd Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
== Word32
lcDysymtab = [Int
32, Int
40, Int
48, Int
56, Int
64, Int
72]
  -- rebase_off, bind_off, weak_bind_off, lazy_bind_off, export_off
  | Word32
cmd Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
== Word32
lcDyldInfo Bool -> Bool -> Bool
|| Word32
cmd Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
== Word32
lcDyldInfoOnly = [Int
8, Int
16, Int
24, Int
32, Int
40]
  -- dataoff, of a linkedit_data_command
  | Word32
cmd Word32 -> [Word32] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Word32]
linkEditDataCommands = [Int
8]
  | Bool
otherwise = []

-- * The format

data Segment = Segment
  { Segment -> ByteString
segName :: B.ByteString,
    -- | where its load command is in the file
    Segment -> Int
segCmd :: Int,
    Segment -> Word64
segVmAddr :: Word64,
    Segment -> Word64
segFileOff :: Word64,
    Segment -> Word64
segFileSize :: Word64,
    -- | its sections: name, and where the section's header is in the file
    Segment -> [(ByteString, Int)]
segSections :: [(B.ByteString, Int)]
  }

-- | The segment @__UNISON@ and where its section @__uc@'s header is
findSection :: B.ByteString -> Maybe (Segment, Int)
findSection :: ByteString -> Maybe (Segment, Int)
findSection ByteString
bytes = do
  Segment
uni <- ByteString -> ByteString -> Maybe Segment
findSegment ByteString
"__UNISON" ByteString
bytes
  Int
sect <- ByteString -> [(ByteString, Int)] -> Maybe Int
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup ByteString
"__uc" (Segment -> [(ByteString, Int)]
segSections Segment
uni)
  pure (Segment
uni, Int
sect)

findSegment :: B.ByteString -> B.ByteString -> Maybe Segment
findSegment :: ByteString -> ByteString -> Maybe Segment
findSegment ByteString
name ByteString
bytes = (Segment -> Bool) -> [Segment] -> Maybe Segment
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
name) (ByteString -> Bool) -> (Segment -> ByteString) -> Segment -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Segment -> ByteString
segName) (ByteString -> [Segment]
segments ByteString
bytes)

segments :: B.ByteString -> [Segment]
segments :: ByteString -> [Segment]
segments ByteString
bytes = [ByteString -> Int -> Segment
segmentAt ByteString
bytes Int
at | ByteString -> Bool
isMachO ByteString
bytes, (Int
at, Word32
cmd) <- ByteString -> [(Int, Word32)]
loadCommands ByteString
bytes, Word32
cmd Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
== Word32
lcSegment64]

segmentAt :: B.ByteString -> Int -> Segment
segmentAt :: ByteString -> Int -> Segment
segmentAt ByteString
bytes Int
at =
  Segment
    { segName :: ByteString
segName = Int -> ByteString
name (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
8),
      segCmd :: Int
segCmd = Int
at,
      segVmAddr :: Word64
segVmAddr = ByteString -> Int -> Word64
u64 ByteString
bytes (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentVmAddr),
      segFileOff :: Word64
segFileOff = ByteString -> Int -> Word64
u64 ByteString
bytes (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentFileOff),
      segFileSize :: Word64
segFileSize = ByteString -> Int -> Word64
u64 ByteString
bytes (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentFileSize),
      segSections :: [(ByteString, Int)]
segSections =
        [ (Int -> ByteString
name Int
sect, Int
sect)
        | Int
k <- [Int
0 .. Word32 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int -> Word32
u32 ByteString
bytes (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentNSects)) Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1],
          let sect :: Int
sect = Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
segmentCommandSize Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
sectionHeaderSize Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
k
        ]
    }
  where
    name :: Int -> ByteString
name Int
field = (Word8 -> Bool) -> ByteString -> ByteString
B.takeWhile (Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
/= Word8
0) (Int -> ByteString -> ByteString
B.take Int
16 (Int -> ByteString -> ByteString
B.drop Int
field ByteString
bytes))

-- | Each load command: where it is in the file, and its type. They follow
-- the header, as many as the header's @ncmds@, each @cmdsize@ long.
loadCommands :: B.ByteString -> [(Int, Word32)]
loadCommands :: ByteString -> [(Int, Word32)]
loadCommands ByteString
bytes = Int -> Word32 -> [(Int, Word32)]
go Int
headerSize (ByteString -> Int -> Word32
u32 ByteString
bytes Int
16)
  where
    go :: Int -> Word32 -> [(Int, Word32)]
    go :: Int -> Word32 -> [(Int, Word32)]
go Int
_ Word32
0 = []
    go Int
at Word32
n = (Int
at, ByteString -> Int -> Word32
u32 ByteString
bytes Int
at) (Int, Word32) -> [(Int, Word32)] -> [(Int, Word32)]
forall a. a -> [a] -> [a]
: Int -> Word32 -> [(Int, Word32)]
go (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Word32 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int -> Word32
u32 ByteString
bytes (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
4))) (Word32
n Word32 -> Word32 -> Word32
forall a. Num a => a -> a -> a
- Word32
1)

-- | Segments are mapped by page; 16 KB covers arm64 and x86-64
pageSize :: Word64
pageSize :: Word64
pageSize = Word64
0x4000

roundUpToPage :: Word64 -> Word64
roundUpToPage :: Word64 -> Word64
roundUpToPage Word64
n = (Word64
n Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Word64
pageSize Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
- Word64
1) Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.&. Word64 -> Word64
forall a. Num a => a -> a
negate Word64
pageSize

mhMagic64 :: Word32
mhMagic64 :: Word32
mhMagic64 = Word32
0xfeedfacf

headerSize :: Int
headerSize :: Int
headerSize = Int
32

lcSegment64, lcSymtab, lcDysymtab, lcDyldInfo, lcDyldInfoOnly :: Word32
lcSegment64 :: Word32
lcSegment64 = Word32
0x19
lcSymtab :: Word32
lcSymtab = Word32
0x2
lcDysymtab :: Word32
lcDysymtab = Word32
0xb
lcDyldInfo :: Word32
lcDyldInfo = Word32
0x22
lcDyldInfoOnly :: Word32
lcDyldInfoOnly = Word32
0x80000022

-- | The load commands that are a @linkedit_data_command@: code signature,
-- segment split info, function starts, data in code, dylib code-signing
-- DRs, linker optimization hints, exports trie, chained fixups
linkEditDataCommands :: [Word32]
linkEditDataCommands :: [Word32]
linkEditDataCommands = [Word32
0x1d, Word32
0x1e, Word32
0x26, Word32
0x29, Word32
0x2b, Word32
0x2e, Word32
0x80000033, Word32
0x80000034]

-- | Offsets within a @segment_command_64@, and its size before its sections
segmentVmAddr, segmentVmSize, segmentFileOff, segmentFileSize, segmentNSects, segmentCommandSize :: Int
segmentVmAddr :: Int
segmentVmAddr = Int
24
segmentVmSize :: Int
segmentVmSize = Int
32
segmentFileOff :: Int
segmentFileOff = Int
40
segmentFileSize :: Int
segmentFileSize = Int
48
segmentNSects :: Int
segmentNSects = Int
64
segmentCommandSize :: Int
segmentCommandSize = Int
72

-- | Offsets within a @section_64@, and its size
sectionSize, sectionOffset, sectionHeaderSize :: Int
sectionSize :: Int
sectionSize = Int
40
sectionOffset :: Int
sectionOffset = Int
48
sectionHeaderSize :: Int
sectionHeaderSize = Int
80

-- * Little-endian fields

u32 :: B.ByteString -> Int -> Word32
u32 :: ByteString -> Int -> Word32
u32 ByteString
bytes Int
at = Word64 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Word64
littleEndian (Int -> ByteString -> ByteString
B.take Int
4 (Int -> ByteString -> ByteString
B.drop Int
at ByteString
bytes)))

u64 :: B.ByteString -> Int -> Word64
u64 :: ByteString -> Int -> Word64
u64 ByteString
bytes Int
at = ByteString -> Word64
littleEndian (Int -> ByteString -> ByteString
B.take Int
8 (Int -> ByteString -> ByteString
B.drop Int
at ByteString
bytes))

littleEndian :: B.ByteString -> Word64
littleEndian :: ByteString -> Word64
littleEndian = (Word8 -> Word64 -> Word64) -> Word64 -> ByteString -> Word64
forall a. (Word8 -> a -> a) -> a -> ByteString -> a
B.foldr' (\Word8
b Word64
acc -> Word64
acc Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`shiftL` Int
8 Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word8
b) Word64
0

put32 :: Int -> Word32 -> B.ByteString -> B.ByteString
put32 :: Int -> Word32 -> ByteString -> ByteString
put32 Int
at Word32
v ByteString
bs = Int -> ByteString -> ByteString
B.take Int
at ByteString
bs ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> [Word8] -> ByteString
B.pack [Word32 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word32
v Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`shiftR` (Int
8 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
k)) | Int
k <- [Int
0 .. Int
3]] ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Int -> ByteString -> ByteString
B.drop (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
4) ByteString
bs

put64 :: Int -> Word64 -> B.ByteString -> B.ByteString
put64 :: Int -> Word64 -> ByteString -> ByteString
put64 Int
at Word64
v ByteString
bs = Int -> ByteString -> ByteString
B.take Int
at ByteString
bs ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> [Word8] -> ByteString
B.pack [Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
v Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`shiftR` (Int
8 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
k)) | Int
k <- [Int
0 .. Int
7]] ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Int -> ByteString -> ByteString
B.drop (Int
at Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
8) ByteString
bs