{"action":"created","comment":{"url":"https://api.github.com/repos/LeastAuthority/wormhole-client/pulls/comments/209150319","pull_request_review_id":145109933,"id":209150319,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIwOTE1MDMxOQ==","diff_hunk":"@@ -0,0 +1,132 @@\n+module Transit.Internal.Pipeline\n+  ( sendPipeline\n+  , receivePipeline\n+  )\n+where\n+\n+import Protolude\n+\n+import Crypto.Hash (SHA256(..))\n+import Data.Conduit ((.|))\n+import Data.ByteString.Builder(toLazyByteString, word32BE)\n+import Data.Binary.Get (getWord32be, runGet)\n+import Crypto.Saltine.Internal.ByteSizes (boxNonce)\n+import System.FilePath ((</>))\n+\n+import qualified Crypto.Hash as Hash\n+import qualified Conduit as C\n+import qualified Data.Conduit.Network as CN\n+import qualified Data.Conduit.Binary as CB\n+import qualified Data.Binary.Builder as BB\n+import qualified Data.ByteString as BS\n+import qualified Data.ByteString.Lazy as BL\n+import qualified Crypto.Saltine.Core.SecretBox as SecretBox\n+import qualified Crypto.Saltine.Class as Saltine\n+\n+import Transit.Internal.Network\n+import Transit.Internal.Crypto\n+\n+-- | Given the peer network socket and the file path to be sent, this Conduit\n+-- pipeline reads the file, encrypts and send it over the network. A sha256\n+-- sum is calculated on the input before encryption to compare with the\n+-- receiver's decrypted copy.\n+sendPipeline :: C.MonadResource m =>\n+                FilePath\n+             -> TCPEndpoint\n+             -> SecretBox.Key\n+             -> C.ConduitM a c m (Text, ())\n+sendPipeline fp (TCPEndpoint s) key =\n+  C.sourceFile fp .| sha256PassThroughC `C.fuseBoth` (encryptC key .| CN.sinkSocket s)\n+\n+-- | Receive the encrypted bytestream from a network socket, decrypt it and\n+-- write it into a file, also calculating the sha256 sum of the decrypted\n+-- output along the way.\n+receivePipeline :: C.MonadResource m =>\n+                   FilePath\n+                -> Int\n+                -> TCPEndpoint\n+                -> SecretBox.Key\n+                -> C.ConduitM a c m (Text, ())\n+receivePipeline fp len (TCPEndpoint s) key =\n+    CN.sourceSocket s\n+    .| assembleRecordC\n+    .| decryptC key\n+    .| CB.isolate len\n+    .| sha256PassThroughC `C.fuseBoth` C.sinkFileCautious (\"./\" </> fp)\n+\n+encryptC :: Monad m => SecretBox.Key -> C.ConduitT ByteString ByteString m ()\n+encryptC key = go Saltine.zero\n+  where\n+    go nonce = do\n+      b <- C.await\n+      case b of\n+        Nothing -> return ()\n+        Just chunk -> do\n+          let cipherText = encrypt key nonce chunk\n+              cipherTextSize = toLazyByteString (word32BE (fromIntegral (BS.length cipherText)))\n+          C.yield (toS cipherTextSize)\n+          C.yield cipherText\n+          go (Saltine.nudge nonce)\n+\n+decryptC :: MonadIO m => SecretBox.Key -> C.ConduitT ByteString ByteString m ()\n+decryptC key = loop\n+  where\n+    loop = do\n+      b <- C.await\n+      case b of\n+        Nothing -> return ()\n+        Just bs -> do\n+          let (nonceBytes, ciphertext) = BS.splitAt boxNonce bs\n+              nonce = fromMaybe (panic \"unable to decode nonce\") $\n+                Saltine.decode nonceBytes\n+              maybePlainText = SecretBox.secretboxOpen key nonce ciphertext\n+          case maybePlainText of\n+            Just plaintext -> do\n+              C.yield plaintext\n+              loop\n+            Nothing -> throwIO (CouldNotDecrypt \"SecretBox failed to open\")\n+\n+sha256PassThroughC :: (Monad m) => C.ConduitT ByteString ByteString m Text\n+sha256PassThroughC = go $! Hash.hashInitWith SHA256\n+  where\n+    go :: (Monad m) => Hash.Context SHA256 -> C.ConduitT ByteString ByteString m Text\n+    go ctx = do\n+      b <- C.await\n+      case b of\n+        Nothing -> return $! show (Hash.hashFinalize ctx)\n+        Just bs -> do\n+          C.yield bs\n+          go $! Hash.hashUpdate ctx bs\n+\n+-- | The decryption conduit computation would succeed only if a complete\n+-- bytestream that represents an encrypted block of data is given to it.\n+-- However, the upstream elements may chunk the data for which one may not\n+-- have control of. The encrypted packet on the wire has a 4-byte length\n+-- header, so we could first read it and assemble a complete encrypted\n+-- block into downstream.\n+assembleRecordC :: Monad m => C.ConduitT ByteString ByteString m ()\n+assembleRecordC = do\n+  b <- C.await\n+  case b of\n+    Nothing -> return ()\n+    Just bs -> do\n+      let (hdr, pkt) = BS.splitAt 4 bs\n+      let len = runGet getWord32be (BL.fromStrict hdr)","path":"src/Transit/Internal/Pipeline.hs","position":114,"original_position":114,"commit_id":"894ea914196332036c00ccb741d48c793e291ac5","original_commit_id":"894ea914196332036c00ccb741d48c793e291ac5","user":{"login":"vu3rdd","id":30785,"node_id":"MDQ6VXNlcjMwNzg1","avatar_url":"https://avatars0.githubusercontent.com/u/30785?v=4","gravatar_id":"","url":"https://api.github.com/users/vu3rdd","html_url":"https://github.com/vu3rdd","followers_url":"https://api.github.com/users/vu3rdd/followers","following_url":"https://api.github.com/users/vu3rdd/following{/other_user}","gists_url":"https://api.github.com/users/vu3rdd/gists{/gist_id}","starred_url":"https://api.github.com/users/vu3rdd/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vu3rdd/subscriptions","organizations_url":"https://api.github.com/users/vu3rdd/orgs","repos_url":"https://api.github.com/users/vu3rdd/repos","events_url":"https://api.github.com/users/vu3rdd/events{/privacy}","received_events_url":"https://api.github.com/users/vu3rdd/received_events","type":"User","site_admin":false},"body":"ooh, that's right. We can end up in a situation where the upstream yields less than 4 bytes and then `runGet` would blow up.\r\n\r\nThe problem is that `runGet` is designed to be used as a non-sink element in a pipeline, so it returns a unit, but passes the chunk to the next downstream block, which means, we probably means, we cannot do a `header <- getChunk 4 BB.empty`. May be I will try another approach where if the yielded bytestring has a length less than 4, it will request a chunk of size 4 using `getChunk` and store the vestiges in the bytestring builder for the next iteration to consume.. ","created_at":"2018-08-10T05:48:17Z","updated_at":"2018-08-10T05:48:17Z","html_url":"https://github.com/LeastAuthority/wormhole-client/pull/19#discussion_r209150319","pull_request_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/pulls/19","author_association":"COLLABORATOR","_links":{"self":{"href":"https://api.github.com/repos/LeastAuthority/wormhole-client/pulls/comments/209150319"},"html":{"href":"https://github.com/LeastAuthority/wormhole-client/pull/19#discussion_r209150319"},"pull_request":{"href":"https://api.github.com/repos/LeastAuthority/wormhole-client/pulls/19"}},"in_reply_to_id":209009069},"pull_request":{"url":"https://api.github.com/repos/LeastAuthority/wormhole-client/pulls/19","id":205102485,"node_id":"MDExOlB1bGxSZXF1ZXN0MjA1MTAyNDg1","html_url":"https://github.com/LeastAuthority/wormhole-client/pull/19","diff_url":"https://github.com/LeastAuthority/wormhole-client/pull/19.diff","patch_url":"https://github.com/LeastAuthority/wormhole-client/pull/19.patch","issue_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/issues/19","number":19,"state":"open","locked":false,"title":"Implement receive conduit pipeline and reorganize the modules a bit","user":{"login":"vu3rdd","id":30785,"node_id":"MDQ6VXNlcjMwNzg1","avatar_url":"https://avatars0.githubusercontent.com/u/30785?v=4","gravatar_id":"","url":"https://api.github.com/users/vu3rdd","html_url":"https://github.com/vu3rdd","followers_url":"https://api.github.com/users/vu3rdd/followers","following_url":"https://api.github.com/users/vu3rdd/following{/other_user}","gists_url":"https://api.github.com/users/vu3rdd/gists{/gist_id}","starred_url":"https://api.github.com/users/vu3rdd/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vu3rdd/subscriptions","organizations_url":"https://api.github.com/users/vu3rdd/orgs","repos_url":"https://api.github.com/users/vu3rdd/repos","events_url":"https://api.github.com/users/vu3rdd/events{/privacy}","received_events_url":"https://api.github.com/users/vu3rdd/received_events","type":"User","site_admin":false},"body":"I moved all the conduit pipeline code into its own module and the cryptography (hashing, encrypt, decrypt etc) into its own Crypto module. There are a few other cleanups. Sending this against the `receive` branch (like the PR #17), as this builds on the things in the `receive` branch. ","created_at":"2018-07-31T12:54:22Z","updated_at":"2018-08-10T05:48:17Z","closed_at":null,"merged_at":null,"merge_commit_sha":"11817b69d2a9868ab0c7c76afbe751a564e321e1","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/pulls/19/commits","review_comments_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/pulls/19/comments","review_comment_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/pulls/comments{/number}","comments_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/issues/19/comments","statuses_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/statuses/894ea914196332036c00ccb741d48c793e291ac5","head":{"label":"LeastAuthority:dev","ref":"dev","sha":"894ea914196332036c00ccb741d48c793e291ac5","user":{"login":"LeastAuthority","id":3720472,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3MjA0NzI=","avatar_url":"https://avatars2.githubusercontent.com/u/3720472?v=4","gravatar_id":"","url":"https://api.github.com/users/LeastAuthority","html_url":"https://github.com/LeastAuthority","followers_url":"https://api.github.com/users/LeastAuthority/followers","following_url":"https://api.github.com/users/LeastAuthority/following{/other_user}","gists_url":"https://api.github.com/users/LeastAuthority/gists{/gist_id}","starred_url":"https://api.github.com/users/LeastAuthority/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/LeastAuthority/subscriptions","organizations_url":"https://api.github.com/users/LeastAuthority/orgs","repos_url":"https://api.github.com/users/LeastAuthority/repos","events_url":"https://api.github.com/users/LeastAuthority/events{/privacy}","received_events_url":"https://api.github.com/users/LeastAuthority/received_events","type":"Organization","site_admin":false},"repo":{"id":136333257,"node_id":"MDEwOlJlcG9zaXRvcnkxMzYzMzMyNTc=","name":"wormhole-client","full_name":"LeastAuthority/wormhole-client","owner":{"login":"LeastAuthority","id":3720472,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3MjA0NzI=","avatar_url":"https://avatars2.githubusercontent.com/u/3720472?v=4","gravatar_id":"","url":"https://api.github.com/users/LeastAuthority","html_url":"https://github.com/LeastAuthority","followers_url":"https://api.github.com/users/LeastAuthority/followers","following_url":"https://api.github.com/users/LeastAuthority/following{/other_user}","gists_url":"https://api.github.com/users/LeastAuthority/gists{/gist_id}","starred_url":"https://api.github.com/users/LeastAuthority/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/LeastAuthority/subscriptions","organizations_url":"https://api.github.com/users/LeastAuthority/orgs","repos_url":"https://api.github.com/users/LeastAuthority/repos","events_url":"https://api.github.com/users/LeastAuthority/events{/privacy}","received_events_url":"https://api.github.com/users/LeastAuthority/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/LeastAuthority/wormhole-client","description":"A magic-wormhole client in Haskell","fork":false,"url":"https://api.github.com/repos/LeastAuthority/wormhole-client","forks_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/forks","keys_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/keys{/key_id}","collaborators_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/teams","hooks_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/hooks","issue_events_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/issues/events{/number}","events_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/events","assignees_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/assignees{/user}","branches_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/branches{/branch}","tags_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/tags","blobs_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/git/refs{/sha}","trees_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/git/trees{/sha}","statuses_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/statuses/{sha}","languages_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/languages","stargazers_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/stargazers","contributors_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/contributors","subscribers_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/subscribers","subscription_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/subscription","commits_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/commits{/sha}","git_commits_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/git/commits{/sha}","comments_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/comments{/number}","issue_comment_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/issues/comments{/number}","contents_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/contents/{+path}","compare_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/compare/{base}...{head}","merges_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/merges","archive_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/downloads","issues_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/issues{/number}","pulls_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/pulls{/number}","milestones_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/milestones{/number}","notifications_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/labels{/name}","releases_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/releases{/id}","deployments_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/deployments","created_at":"2018-06-06T13:32:59Z","updated_at":"2018-08-09T13:54:26Z","pushed_at":"2018-08-09T16:06:03Z","git_url":"git://github.com/LeastAuthority/wormhole-client.git","ssh_url":"git@github.com:LeastAuthority/wormhole-client.git","clone_url":"https://github.com/LeastAuthority/wormhole-client.git","svn_url":"https://github.com/LeastAuthority/wormhole-client","homepage":null,"size":205,"stargazers_count":0,"watchers_count":0,"language":"Haskell","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":19,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":0,"open_issues":19,"watchers":0,"default_branch":"master"}},"base":{"label":"LeastAuthority:receive","ref":"receive","sha":"6055164ca2f169ccbb2607af8a0fb448264dfa6a","user":{"login":"LeastAuthority","id":3720472,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3MjA0NzI=","avatar_url":"https://avatars2.githubusercontent.com/u/3720472?v=4","gravatar_id":"","url":"https://api.github.com/users/LeastAuthority","html_url":"https://github.com/LeastAuthority","followers_url":"https://api.github.com/users/LeastAuthority/followers","following_url":"https://api.github.com/users/LeastAuthority/following{/other_user}","gists_url":"https://api.github.com/users/LeastAuthority/gists{/gist_id}","starred_url":"https://api.github.com/users/LeastAuthority/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/LeastAuthority/subscriptions","organizations_url":"https://api.github.com/users/LeastAuthority/orgs","repos_url":"https://api.github.com/users/LeastAuthority/repos","events_url":"https://api.github.com/users/LeastAuthority/events{/privacy}","received_events_url":"https://api.github.com/users/LeastAuthority/received_events","type":"Organization","site_admin":false},"repo":{"id":136333257,"node_id":"MDEwOlJlcG9zaXRvcnkxMzYzMzMyNTc=","name":"wormhole-client","full_name":"LeastAuthority/wormhole-client","owner":{"login":"LeastAuthority","id":3720472,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3MjA0NzI=","avatar_url":"https://avatars2.githubusercontent.com/u/3720472?v=4","gravatar_id":"","url":"https://api.github.com/users/LeastAuthority","html_url":"https://github.com/LeastAuthority","followers_url":"https://api.github.com/users/LeastAuthority/followers","following_url":"https://api.github.com/users/LeastAuthority/following{/other_user}","gists_url":"https://api.github.com/users/LeastAuthority/gists{/gist_id}","starred_url":"https://api.github.com/users/LeastAuthority/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/LeastAuthority/subscriptions","organizations_url":"https://api.github.com/users/LeastAuthority/orgs","repos_url":"https://api.github.com/users/LeastAuthority/repos","events_url":"https://api.github.com/users/LeastAuthority/events{/privacy}","received_events_url":"https://api.github.com/users/LeastAuthority/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/LeastAuthority/wormhole-client","description":"A magic-wormhole client in Haskell","fork":false,"url":"https://api.github.com/repos/LeastAuthority/wormhole-client","forks_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/forks","keys_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/keys{/key_id}","collaborators_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/teams","hooks_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/hooks","issue_events_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/issues/events{/number}","events_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/events","assignees_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/assignees{/user}","branches_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/branches{/branch}","tags_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/tags","blobs_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/git/refs{/sha}","trees_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/git/trees{/sha}","statuses_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/statuses/{sha}","languages_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/languages","stargazers_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/stargazers","contributors_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/contributors","subscribers_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/subscribers","subscription_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/subscription","commits_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/commits{/sha}","git_commits_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/git/commits{/sha}","comments_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/comments{/number}","issue_comment_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/issues/comments{/number}","contents_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/contents/{+path}","compare_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/compare/{base}...{head}","merges_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/merges","archive_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/downloads","issues_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/issues{/number}","pulls_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/pulls{/number}","milestones_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/milestones{/number}","notifications_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/labels{/name}","releases_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/releases{/id}","deployments_url":"https://api.github.com/repos/LeastAuthority/wormhole-client/deployments","created_at":"2018-06-06T13:32:59Z","updated_at":"2018-08-09T13:54:26Z","pushed_at":"2018-08-09T16:06:03Z","git_url":"git://github.com/LeastAuthority/wormhole-client.git","ssh_url":"git@github.com:LeastAuthority/wormhole-client.git","clone_url":"https://github.com/LeastAuthority/wormhole-client.git","svn_url":"https://github.com/LeastAuthority/wormhole-client","homepage":null,"size":205,"stargazers_count":0,"watchers_count":0,"language":"Haskell","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":19,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":0,"open_issues":19,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/LeastAuthority/wormhole-client/pulls/19"},"html":{"href":"https://github.com/LeastAuthority/wormhole-client/pull/19"},"issue":{"href":"https://api.github.com/repos/LeastAuthority/wormhole-client/issues/19"},"comments":{"href":"https://api.github.com/repos/LeastAuthority/wormhole-client/issues/19/comments"},"review_comments":{"href":"https://api.github.com/repos/LeastAuthority/wormhole-client/pulls/19/comments"},"review_comment":{"href":"https://api.github.com/repos/LeastAuthority/wormhole-client/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/LeastAuthority/wormhole-client/pulls/19/commits"},"statuses":{"href":"https://api.github.com/repos/LeastAuthority/wormhole-client/statuses/894ea914196332036c00ccb741d48c793e291ac5"}},"author_association":"COLLABORATOR"}}
{"actor":{"display_login":"vu3rdd"}}