{"action":"created","comment":{"url":"https://api.github.com/repos/google/openhtf/pulls/comments/52260947","id":52260947,"diff_hunk":"@@ -0,0 +1,134 @@\n+# Copyright 2016 Google Inc. All Rights Reserved.\n+\n+# Licensed under the Apache License, Version 2.0 (the \"License\");\n+# you may not use this file except in compliance with the License.\n+# You may obtain a copy of the License at\n+\n+#     http://www.apache.org/licenses/LICENSE-2.0\n+\n+# Unless required by applicable law or agreed to in writing, software\n+# distributed under the License is distributed on an \"AS IS\" BASIS,\n+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+# See the License for the specific language governing permissions and\n+# limitations under the License.\n+\n+\n+\"\"\"Multicast facilities for sending and receiving messages.\n+\n+This module includes both a MulticastListener that listens on a multicast\n+socket and invokes a callback function for each message recieved, and a send()\n+function that is used to send one-shot messages to a multicast socket.\n+\"\"\"\n+\n+\n+import logging\n+import socket\n+import struct\n+import threading\n+\n+\n+_LOG = logging.getLogger(__name__)\n+\n+DEFAULT_ADDRESS = '239.1.1.1'\n+DEFAULT_PORT = 10000\n+DEFAULT_TTL = 1\n+MAX_MESSAGE_BYTES = 1024  # Maximum allowable message length in bytes.\n+PING_STRING = 'OPENHTF_PING'\n+PING_RESPONSE_KEY = 'OPENHTF_PING_RESPONSE'\n+\n+\n+class MulticastListener(threading.Thread):\n+  \"\"\"Agent that listens (and responds) to short messages on a multicast socket.\n+\n+  Args:\n+    callback: A callable to invoke upon receipt of a multicast message. Will be\n+              called with one argument -- the text of the message received.\n+              callback can optionally return a string response, which will be\n+              transmitted back to the sender.\n+    address: Multicast IP address component of the socket to listen on.\n+    port: Multicast UDP port component of the socket to listen on.\n+    ttl: TTL for multicast messages. 1 to keep traffic in-network.\n+  \"\"\"\n+  LISTEN_TIMEOUT_S = 3  # Seconds to listen before retrying.\n+\n+  def __init__(self,\n+               callback,\n+               address=DEFAULT_ADDRESS,\n+               port=DEFAULT_PORT,\n+               ttl=DEFAULT_TTL):\n+    super(MulticastListener, self).__init__()\n+    self.address = address\n+    self.port = port\n+    self.ttl = ttl\n+    self._callback = callback\n+    self._live = False\n+    self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n+    self._sock.setsockopt(socket.IPPROTO_IP,\n+                          socket.IP_MULTICAST_TTL,\n+                          self.ttl)\n+\n+  def Stop(self):\n+    \"\"\"Stop listening for messages.\"\"\"\n+    self._live = False\n+    self.join()\n+\n+  def run(self):\n+    \"\"\"Listen for pings until stopped.\"\"\"\n+    self._live = True\n+    self._sock.bind(('', self.port))\n+    self._sock.settimeout(self.LISTEN_TIMEOUT_S)\n+    self._sock.setsockopt(\n+        socket.IPPROTO_IP,\n+        socket.IP_ADD_MEMBERSHIP,\n+        struct.pack(\n+            '!4sL',\n+            socket.inet_aton(self.address),\n+            socket.INADDR_ANY))\n+\n+    while self._live:\n+      try:\n+        data, address = self._sock.recvfrom(MAX_MESSAGE_BYTES)\n+        _LOG.debug('Received multicast message from %s: %s'% (address, data))\n+        response = self._callback(data)\n+        if response:","path":"openhtf/util/multicast.py","position":93,"original_position":93,"commit_id":"7e4bbfd7c0254289f30616aa23d7c46b9fc1f1b9","original_commit_id":"c86e20510beccbec7c256e6a0fe7ddb837e895cf","user":{"login":"jettisonjoe","id":8150422,"avatar_url":"https://avatars.githubusercontent.com/u/8150422?v=3","gravatar_id":"","url":"https://api.github.com/users/jettisonjoe","html_url":"https://github.com/jettisonjoe","followers_url":"https://api.github.com/users/jettisonjoe/followers","following_url":"https://api.github.com/users/jettisonjoe/following{/other_user}","gists_url":"https://api.github.com/users/jettisonjoe/gists{/gist_id}","starred_url":"https://api.github.com/users/jettisonjoe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jettisonjoe/subscriptions","organizations_url":"https://api.github.com/users/jettisonjoe/orgs","repos_url":"https://api.github.com/users/jettisonjoe/repos","events_url":"https://api.github.com/users/jettisonjoe/events{/privacy}","received_events_url":"https://api.github.com/users/jettisonjoe/received_events","type":"User","site_admin":false},"body":"Fair enough. Fixed.","created_at":"2016-02-09T02:30:56Z","updated_at":"2016-02-09T02:30:56Z","html_url":"https://github.com/google/openhtf/pull/59#discussion_r52260947","pull_request_url":"https://api.github.com/repos/google/openhtf/pulls/59","_links":{"self":{"href":"https://api.github.com/repos/google/openhtf/pulls/comments/52260947"},"html":{"href":"https://github.com/google/openhtf/pull/59#discussion_r52260947"},"pull_request":{"href":"https://api.github.com/repos/google/openhtf/pulls/59"}}},"pull_request":{"url":"https://api.github.com/repos/google/openhtf/pulls/59","id":51848398,"html_url":"https://github.com/google/openhtf/pull/59","diff_url":"https://github.com/google/openhtf/pull/59.diff","patch_url":"https://github.com/google/openhtf/pull/59.patch","issue_url":"https://api.github.com/repos/google/openhtf/issues/59","number":59,"state":"open","locked":false,"title":"Multicast station discovery service.","user":{"login":"jettisonjoe","id":8150422,"avatar_url":"https://avatars.githubusercontent.com/u/8150422?v=3","gravatar_id":"","url":"https://api.github.com/users/jettisonjoe","html_url":"https://github.com/jettisonjoe","followers_url":"https://api.github.com/users/jettisonjoe/followers","following_url":"https://api.github.com/users/jettisonjoe/following{/other_user}","gists_url":"https://api.github.com/users/jettisonjoe/gists{/gist_id}","starred_url":"https://api.github.com/users/jettisonjoe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jettisonjoe/subscriptions","organizations_url":"https://api.github.com/users/jettisonjoe/orgs","repos_url":"https://api.github.com/users/jettisonjoe/repos","events_url":"https://api.github.com/users/jettisonjoe/events{/privacy}","received_events_url":"https://api.github.com/users/jettisonjoe/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-11-25T21:32:17Z","updated_at":"2016-02-09T02:30:56Z","closed_at":null,"merged_at":null,"merge_commit_sha":"689b5b191edb2fb196ac611fc16699ea07745d1c","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/google/openhtf/pulls/59/commits","review_comments_url":"https://api.github.com/repos/google/openhtf/pulls/59/comments","review_comment_url":"https://api.github.com/repos/google/openhtf/pulls/comments{/number}","comments_url":"https://api.github.com/repos/google/openhtf/issues/59/comments","statuses_url":"https://api.github.com/repos/google/openhtf/statuses/7e4bbfd7c0254289f30616aa23d7c46b9fc1f1b9","head":{"label":"jettisonjoe:master","ref":"master","sha":"7e4bbfd7c0254289f30616aa23d7c46b9fc1f1b9","user":{"login":"jettisonjoe","id":8150422,"avatar_url":"https://avatars.githubusercontent.com/u/8150422?v=3","gravatar_id":"","url":"https://api.github.com/users/jettisonjoe","html_url":"https://github.com/jettisonjoe","followers_url":"https://api.github.com/users/jettisonjoe/followers","following_url":"https://api.github.com/users/jettisonjoe/following{/other_user}","gists_url":"https://api.github.com/users/jettisonjoe/gists{/gist_id}","starred_url":"https://api.github.com/users/jettisonjoe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jettisonjoe/subscriptions","organizations_url":"https://api.github.com/users/jettisonjoe/orgs","repos_url":"https://api.github.com/users/jettisonjoe/repos","events_url":"https://api.github.com/users/jettisonjoe/events{/privacy}","received_events_url":"https://api.github.com/users/jettisonjoe/received_events","type":"User","site_admin":false},"repo":{"id":41824189,"name":"openhtf","full_name":"jettisonjoe/openhtf","owner":{"login":"jettisonjoe","id":8150422,"avatar_url":"https://avatars.githubusercontent.com/u/8150422?v=3","gravatar_id":"","url":"https://api.github.com/users/jettisonjoe","html_url":"https://github.com/jettisonjoe","followers_url":"https://api.github.com/users/jettisonjoe/followers","following_url":"https://api.github.com/users/jettisonjoe/following{/other_user}","gists_url":"https://api.github.com/users/jettisonjoe/gists{/gist_id}","starred_url":"https://api.github.com/users/jettisonjoe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jettisonjoe/subscriptions","organizations_url":"https://api.github.com/users/jettisonjoe/orgs","repos_url":"https://api.github.com/users/jettisonjoe/repos","events_url":"https://api.github.com/users/jettisonjoe/events{/privacy}","received_events_url":"https://api.github.com/users/jettisonjoe/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jettisonjoe/openhtf","description":null,"fork":true,"url":"https://api.github.com/repos/jettisonjoe/openhtf","forks_url":"https://api.github.com/repos/jettisonjoe/openhtf/forks","keys_url":"https://api.github.com/repos/jettisonjoe/openhtf/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jettisonjoe/openhtf/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jettisonjoe/openhtf/teams","hooks_url":"https://api.github.com/repos/jettisonjoe/openhtf/hooks","issue_events_url":"https://api.github.com/repos/jettisonjoe/openhtf/issues/events{/number}","events_url":"https://api.github.com/repos/jettisonjoe/openhtf/events","assignees_url":"https://api.github.com/repos/jettisonjoe/openhtf/assignees{/user}","branches_url":"https://api.github.com/repos/jettisonjoe/openhtf/branches{/branch}","tags_url":"https://api.github.com/repos/jettisonjoe/openhtf/tags","blobs_url":"https://api.github.com/repos/jettisonjoe/openhtf/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jettisonjoe/openhtf/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jettisonjoe/openhtf/git/refs{/sha}","trees_url":"https://api.github.com/repos/jettisonjoe/openhtf/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jettisonjoe/openhtf/statuses/{sha}","languages_url":"https://api.github.com/repos/jettisonjoe/openhtf/languages","stargazers_url":"https://api.github.com/repos/jettisonjoe/openhtf/stargazers","contributors_url":"https://api.github.com/repos/jettisonjoe/openhtf/contributors","subscribers_url":"https://api.github.com/repos/jettisonjoe/openhtf/subscribers","subscription_url":"https://api.github.com/repos/jettisonjoe/openhtf/subscription","commits_url":"https://api.github.com/repos/jettisonjoe/openhtf/commits{/sha}","git_commits_url":"https://api.github.com/repos/jettisonjoe/openhtf/git/commits{/sha}","comments_url":"https://api.github.com/repos/jettisonjoe/openhtf/comments{/number}","issue_comment_url":"https://api.github.com/repos/jettisonjoe/openhtf/issues/comments{/number}","contents_url":"https://api.github.com/repos/jettisonjoe/openhtf/contents/{+path}","compare_url":"https://api.github.com/repos/jettisonjoe/openhtf/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jettisonjoe/openhtf/merges","archive_url":"https://api.github.com/repos/jettisonjoe/openhtf/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jettisonjoe/openhtf/downloads","issues_url":"https://api.github.com/repos/jettisonjoe/openhtf/issues{/number}","pulls_url":"https://api.github.com/repos/jettisonjoe/openhtf/pulls{/number}","milestones_url":"https://api.github.com/repos/jettisonjoe/openhtf/milestones{/number}","notifications_url":"https://api.github.com/repos/jettisonjoe/openhtf/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jettisonjoe/openhtf/labels{/name}","releases_url":"https://api.github.com/repos/jettisonjoe/openhtf/releases{/id}","deployments_url":"https://api.github.com/repos/jettisonjoe/openhtf/deployments","created_at":"2015-09-02T19:50:25Z","updated_at":"2016-01-12T23:54:07Z","pushed_at":"2016-01-26T23:48:06Z","git_url":"git://github.com/jettisonjoe/openhtf.git","ssh_url":"git@github.com:jettisonjoe/openhtf.git","clone_url":"https://github.com/jettisonjoe/openhtf.git","svn_url":"https://github.com/jettisonjoe/openhtf","homepage":null,"size":18345,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":false,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"google:master","ref":"master","sha":"56bf44e75336b3215304533227ed98553b36da96","user":{"login":"google","id":1342004,"avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=3","gravatar_id":"","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"repo":{"id":41519483,"name":"openhtf","full_name":"google/openhtf","owner":{"login":"google","id":1342004,"avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=3","gravatar_id":"","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/google/openhtf","description":null,"fork":false,"url":"https://api.github.com/repos/google/openhtf","forks_url":"https://api.github.com/repos/google/openhtf/forks","keys_url":"https://api.github.com/repos/google/openhtf/keys{/key_id}","collaborators_url":"https://api.github.com/repos/google/openhtf/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/google/openhtf/teams","hooks_url":"https://api.github.com/repos/google/openhtf/hooks","issue_events_url":"https://api.github.com/repos/google/openhtf/issues/events{/number}","events_url":"https://api.github.com/repos/google/openhtf/events","assignees_url":"https://api.github.com/repos/google/openhtf/assignees{/user}","branches_url":"https://api.github.com/repos/google/openhtf/branches{/branch}","tags_url":"https://api.github.com/repos/google/openhtf/tags","blobs_url":"https://api.github.com/repos/google/openhtf/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/google/openhtf/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/google/openhtf/git/refs{/sha}","trees_url":"https://api.github.com/repos/google/openhtf/git/trees{/sha}","statuses_url":"https://api.github.com/repos/google/openhtf/statuses/{sha}","languages_url":"https://api.github.com/repos/google/openhtf/languages","stargazers_url":"https://api.github.com/repos/google/openhtf/stargazers","contributors_url":"https://api.github.com/repos/google/openhtf/contributors","subscribers_url":"https://api.github.com/repos/google/openhtf/subscribers","subscription_url":"https://api.github.com/repos/google/openhtf/subscription","commits_url":"https://api.github.com/repos/google/openhtf/commits{/sha}","git_commits_url":"https://api.github.com/repos/google/openhtf/git/commits{/sha}","comments_url":"https://api.github.com/repos/google/openhtf/comments{/number}","issue_comment_url":"https://api.github.com/repos/google/openhtf/issues/comments{/number}","contents_url":"https://api.github.com/repos/google/openhtf/contents/{+path}","compare_url":"https://api.github.com/repos/google/openhtf/compare/{base}...{head}","merges_url":"https://api.github.com/repos/google/openhtf/merges","archive_url":"https://api.github.com/repos/google/openhtf/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/google/openhtf/downloads","issues_url":"https://api.github.com/repos/google/openhtf/issues{/number}","pulls_url":"https://api.github.com/repos/google/openhtf/pulls{/number}","milestones_url":"https://api.github.com/repos/google/openhtf/milestones{/number}","notifications_url":"https://api.github.com/repos/google/openhtf/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/google/openhtf/labels{/name}","releases_url":"https://api.github.com/repos/google/openhtf/releases{/id}","deployments_url":"https://api.github.com/repos/google/openhtf/deployments","created_at":"2015-08-28T01:14:17Z","updated_at":"2016-01-14T11:08:48Z","pushed_at":"2016-02-09T02:11:56Z","git_url":"git://github.com/google/openhtf.git","ssh_url":"git@github.com:google/openhtf.git","clone_url":"https://github.com/google/openhtf.git","svn_url":"https://github.com/google/openhtf","homepage":null,"size":18363,"stargazers_count":16,"watchers_count":16,"language":"Python","has_issues":true,"has_downloads":false,"has_wiki":true,"has_pages":false,"forks_count":16,"mirror_url":null,"open_issues_count":40,"forks":16,"open_issues":40,"watchers":16,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/google/openhtf/pulls/59"},"html":{"href":"https://github.com/google/openhtf/pull/59"},"issue":{"href":"https://api.github.com/repos/google/openhtf/issues/59"},"comments":{"href":"https://api.github.com/repos/google/openhtf/issues/59/comments"},"review_comments":{"href":"https://api.github.com/repos/google/openhtf/pulls/59/comments"},"review_comment":{"href":"https://api.github.com/repos/google/openhtf/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/google/openhtf/pulls/59/commits"},"statuses":{"href":"https://api.github.com/repos/google/openhtf/statuses/7e4bbfd7c0254289f30616aa23d7c46b9fc1f1b9"}}}}