Как создать два ByteStrings вызова этой внешней библиотеки API?
В настоящее время я пишу привязки к криптографической библиотеке, которая предоставляет функцию для генерации ключевых пар:
const size_t PUBLICKEYBYTES = 32;
const size_t SECRETKEYBYTES = 32;
int random_keypair(unsigned char pk[PUBLICKEYBYTES],
unsigned char sk[SECRETKEYBYTES]);
Эта функция случайным образом генерирует секретный ключ, вычисляет соответствующий открытый ключ и помещает результаты в pk
и sk
.
Когда я просто возвращаю один ByteString
, я обнаружил, что самый простой способ-использовать create :: Int -> (Ptr Word8 -> IO ()) -> IO ByteString
из Data.ByteString.Internal
. Однако эта функция не может создать два ByteStrings
одновременно.
Мой первый подход состоял в том, чтобы написать что-то например:
newtype PublicKey = PublicKey ByteString
newtype SecretKey = SecretKey ByteString
randomKeypair :: IO (PublicKey, SecretKey)
randomKeypair = do
let pk = B.replicate 0 publicKeyBytes
sk = B.replicate 0 secretKeyBytes
B.unsafeUseAsCString pk $ ppk ->
B.unsafeUseAsCString sk $ psk ->
c_random_keypair ppk psk
return (PublicKey pk, SecretKey sk)
Однако это, похоже, не работает с GHC 7.10.2. При запуске набора тестов я обнаруживаю, что у меня, кажется, есть совместное использование ByteString
s между вызовами функций, что приводит к сбою шифрования/дешифрования и дает неправильные результаты.
Мне удалось обойти эту проблему, определив свою собственную функцию:
createWithResult :: Int -> (Ptr Word8 -> IO a) -> IO (ByteString, a)
createWithResult i f = do
fp <- B.mallocByteString i
r <- withForeignPtr fp f
return (B.fromForeignPtr fp 0 i, r)
И использовать его как:
randomKeypair = fmap (PublicKey *** SecretKey) $
createWithResult publicKeyBytes $ ppk ->
B.create secretKeyBytes $ psk ->
void $ c_random_keypair ppk psk
Это, кажется, работает, все тесты проходят.
Мой вопрос в том, что же такое семантика когда речь заходит о совместном использовании и ссылочной прозрачности, когда речь заходит о монаде IO
?
let
- утверждения могут быть переведены в определения верхнего уровня, и это было причиной, по которой я получил эти проблемы.2 ответа:
Проблема с вашим первым подходом заключается в том, что вы пытаетесь изменить неизменяемое значение (
pk
иsk
в вашей функции). Документы для unsafeUseAsCString говорят:Изменение строки C, либо в C, либо с помощью poke, приведет к изменению содержимого ByteString, нарушая ссылочную прозрачность
Монада
IO
не имеет другой семантики, когда речь заходит о совместном использовании и ссылочной прозрачности. На самом деле,let
вdo
блок никоим образом не связан сIO
монадой; ваш код эквивалентен:Теперь ясно видно, чтоrandomKeypair :: IO (PublicKey, SecretKey) randomKeypair = let pk = B.replicate 0 publicKeyBytes sk = B.replicate 0 secretKeyBytes in B.unsafeUseAsCString pk (\ppk -> B.unsafeUseAsCString sk $ \psk -> c_random_keypair ppk psk) >> return (PublicKey pk, SecretKey sk)
pk
иsk
могут быть подняты на верхний уровень.
Это не ответ на ваш вопрос, но это слишком долго, чтобы быть помещенным в комментарий.
В качестве Хака, если вы хотите избежать ручного распределения, вы можете использовать два вложенных вызова
create
иIORef ByteString
для хранения байтестринга, созданного самым внутреннимcreate
. Например (псевдокод)Однако я предпочитаю вашsecRef <- newIORef "" pubB <- create publicKeyBytes (\pub -> do secB <- create secretKeyBytes (\sec -> void $ c_random_keypair pub sec) writeIORef secRef secB) secB <- readIORef secRef return (pubB, secB)
createWithResult
этому подходу.