{"action":"created","comment":{"url":"https://api.github.com/repos/bakerstu/openmrn/pulls/comments/238069951","pull_request_review_id":180529841,"id":238069951,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzODA2OTk1MQ==","diff_hunk":"@@ -0,0 +1,553 @@\n+/** \\copyright\n+ * Copyright (c) 2017, Balazs Racz\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or without\n+ * modification, are permitted provided that the following conditions are met:\n+ *\n+ *  - Redistributions of source code must retain the above copyright notice,\n+ *    this list of conditions and the following disclaimer.\n+ *\n+ *  - Redistributions in binary form must reproduce the above copyright notice,\n+ *    this list of conditions and the following disclaimer in the documentation\n+ *    and/or other materials provided with the distribution.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n+ * POSSIBILITY OF SUCH DAMAGE.\n+ *\n+ * \\file IfTcpImpl.hxx\n+ *\n+ * Internal implementation flows for the Tcp interface\n+ *\n+ * @author Balazs Racz\n+ * @date 16 Apr 2017\n+ */\n+\n+#ifndef _OPENLCB_IFTCPIMPL_HXX_\n+#define _OPENLCB_IFTCPIMPL_HXX_\n+\n+#include \"openlcb/If.hxx\"\n+#include \"utils/Hub.hxx\"\n+#include \"utils/HubDeviceSelect.hxx\"\n+\n+namespace openlcb\n+{\n+\n+/** Static class for constants and utilities related to the TCP transport\n+ * protocol. */\n+class TcpDefs\n+{\n+public:\n+    /** Renders a TCP message into a single buffer, ready to transmit.\n+     * @param msg is the OpenLCB message to render.\n+     * @param gateway_node_id will be populated into the message header as the\n+     * message source (last sending node ID).\n+     * @param sequence is a 48-bit millisecond value that's monotonic.\n+     * @param target is the buffer into which to render the message. */\n+    static void render_tcp_message(const GenMessage &msg,\n+        NodeID gateway_node_id, long long sequence, string *tgt)\n+    {\n+        bool has_dst = Defs::get_mti_address(msg.mti);\n+        HASSERT(tgt);\n+        string &target = *tgt;\n+        target.assign(HDR_LEN + msg.payload.size() +\n+                (has_dst ? MSG_ADR_PAYLOAD_OFS : MSG_GLOBAL_PAYLOAD_OFS),\n+            '\\0');\n+        uint16_t flags = FLAGS_OPENLCB_MSG;\n+        error_to_data(flags, &target[HDR_FLAG_OFS]);\n+        unsigned sz = target.size() - HDR_SIZE_END;\n+        target[HDR_SIZE_OFS] = (sz >> 16) & 0xff;\n+        target[HDR_SIZE_OFS + 1] = (sz >> 8) & 0xff;\n+        target[HDR_SIZE_OFS + 2] = sz & 0xff;\n+        node_id_to_data(gateway_node_id, &target[HDR_GATEWAY_OFS]);\n+        node_id_to_data(sequence, &target[HDR_TIMESTAMP_OFS]);\n+        error_to_data(msg.mti, &target[HDR_LEN + MSG_MTI_OFS]);\n+        node_id_to_data(msg.src.id, &target[HDR_LEN + MSG_SRC_OFS]);\n+        if (has_dst)\n+        {\n+            node_id_to_data(msg.dst.id, &target[HDR_LEN + MSG_DST_OFS]);\n+            memcpy(&target[HDR_LEN + MSG_ADR_PAYLOAD_OFS], msg.payload.data(),\n+                msg.payload.size());\n+        }\n+        else\n+        {\n+            memcpy(&target[HDR_LEN + MSG_GLOBAL_PAYLOAD_OFS],\n+                msg.payload.data(), msg.payload.size());\n+        }\n+    }\n+\n+    /// Guesses the length of a tcp message from looking at the prefix of the\n+    /// payload.\n+    ///\n+    /// @param data is the prefix of the incoming stream.\n+    /// @param len is the number of available bytes in this prefix.\n+    /// @return -1 if there is not enough bytes in the prefix yet to know the\n+    /// length. Otherwise the total number of bytes that constitute this\n+    /// packet.\n+    static int get_tcp_message_len(const void *data, size_t len)\n+    {\n+        if (len < HDR_SIZE_END)\n+        {\n+            return -1;\n+        }\n+        const uint8_t *src = static_cast<const uint8_t *>(data);\n+        uint32_t sz = src[HDR_SIZE_OFS];\n+        sz <<= 8;\n+        sz |= src[HDR_SIZE_OFS + 1];\n+        sz <<= 8;\n+        sz |= src[HDR_SIZE_OFS + 2];\n+        return sz + HDR_SIZE_END;\n+    }\n+\n+    /// Parses a TCP message format (from binary payload) into a general\n+    /// OpenLCB message.\n+    /// @param src the rendered TCP message.\n+    /// @param tgt the output generic message\n+    /// @return false if the message is not well formatted or\n+    /// it is not an OpenLCB message.\n+    static bool parse_tcp_message(const string &src, GenMessage *tgt)\n+    {\n+        int expected_size = get_tcp_message_len(src.data(), src.size());\n+        if (expected_size > (int)src.size() || expected_size < MIN_MESSAGE_SIZE)\n+        {\n+            LOG(WARNING, \"Incomplete or incorrectly formatted TCP message.\");\n+            return false;\n+        }\n+        uint16_t flags = data_to_error(&src[0]);\n+        if ((flags & FLAGS_OPENLCB_MSG) == 0)\n+        {\n+            return false;\n+        }\n+        HASSERT((flags & FLAGS_CHAINING) == 0);\n+        tgt->flagsDst = 0;\n+        tgt->flagsSrc = 0;\n+        if (flags & FLAGS_FRAGMENT_NOT_FIRST)\n+        {\n+            tgt->set_flag_dst(GenMessage::DSTFLAG_NOT_FIRST_MESSAGE);\n+        }\n+        if (flags & FLAGS_FRAGMENT_NOT_LAST)\n+        {\n+            tgt->set_flag_dst(GenMessage::DSTFLAG_NOT_LAST_MESSAGE);\n+        }\n+        // Contents of the size field in the header.\n+        uint32_t sz = (uint8_t)src[HDR_SIZE_OFS];\n+        sz <<= 8;\n+        sz |= (uint8_t)src[HDR_SIZE_OFS + 1];\n+        sz <<= 8;\n+        sz |= (uint8_t)src[HDR_SIZE_OFS + 2];\n+        if ((sz + HDR_SIZE_OFS) > src.size())\n+        {\n+            return false;\n+        }\n+        const char *msg = &src[HDR_LEN];\n+        // We have already checked the size to be long enough for a global\n+        // message with 0 bytes payload. So source address and MTI is ok to\n+        // parse now.\n+        tgt->mti = (Defs::MTI)data_to_error(msg + MSG_MTI_OFS);\n+        tgt->src.clear();\n+        tgt->dst.clear();\n+        tgt->src.id = data_to_node_id(msg + MSG_SRC_OFS);\n+        // now contains the length of the message after the header.\n+        int payload_bytes = expected_size - HDR_LEN;\n+        int payload_ofs;\n+        if (Defs::get_mti_address(tgt->mti))\n+        {\n+            payload_ofs = MSG_ADR_PAYLOAD_OFS;\n+            tgt->dst.id = data_to_node_id(msg + MSG_DST_OFS);\n+        }\n+        else\n+        {\n+            payload_ofs = MSG_GLOBAL_PAYLOAD_OFS;\n+        }\n+        payload_bytes -= payload_ofs;\n+        if (payload_bytes < 0)\n+        {\n+            LOG(WARNING, \"TCP message to short.\");\n+            return false;\n+        }\n+        tgt->payload.assign(msg + payload_ofs, payload_bytes);\n+        return true;\n+    }\n+\n+    /// @param tcp_payload is a message holding a TCP protocol frame.\n+    /// @return the OpenLCB priority in range 0..3 or uint_max if the message\n+    /// is broken.\n+    static unsigned guess_priority(const string &tcp_payload)\n+    {\n+        if (tcp_payload.size() < ABS_MTI_OFS + 2)\n+        {\n+            return UINT_MAX;\n+        }\n+        auto mti = (Defs::MTI)data_to_error(tcp_payload.data() + ABS_MTI_OFS);\n+        return Defs::mti_priority(mti);\n+    }\n+\n+    enum\n+    {\n+        FLAGS_OPENLCB_MSG = 0x8000,\n+        FLAGS_CHAINING = 0x4000,\n+        FLAGS_RESVD1_ZERO_CHECK = 0x3000,\n+        FLAGS_FRAGMENT_NOT_FIRST = 0x0800,\n+        FLAGS_FRAGMENT_NOT_LAST = 0x0400,\n+        FLAGS_RESVD2_IGNORED = 0x03FF,\n+\n+        HDR_FLAG_OFS = 0,\n+        HDR_SIZE_OFS = 2,\n+        HDR_GATEWAY_OFS = 2 + 3,\n+        HDR_SIZE_END = HDR_GATEWAY_OFS,\n+        HDR_TIMESTAMP_OFS = 2 + 3 + 6,\n+        HDR_LEN = 2 + 3 + 6 + 6,\n+\n+        MSG_MTI_OFS = 0,\n+        MSG_SRC_OFS = 2,\n+        MSG_DST_OFS = 2 + 6,\n+        MSG_ADR_PAYLOAD_OFS = 2 + 6 + 6,\n+\n+        MSG_GLOBAL_PAYLOAD_OFS = MSG_DST_OFS,\n+\n+        MIN_MESSAGE_SIZE = MSG_GLOBAL_PAYLOAD_OFS + HDR_LEN,\n+        MIN_ADR_MESSAGE_SIZE = MSG_ADR_PAYLOAD_OFS + HDR_LEN,\n+\n+        ABS_MTI_OFS = HDR_LEN + MSG_MTI_OFS,\n+    };\n+\n+private:\n+    /// No usable constructor; this is a static-only class.\n+    TcpDefs();\n+};\n+\n+/**\n+   Virtual clock interface. Implementations are not required to be thread-safe.\n+*/\n+class SequenceNumberGenerator : public Destructable\n+{\n+public:\n+    /** Returns the next strictly monotonic sequence number. */\n+    virtual long long get_sequence_number() = 0;\n+};\n+\n+/**\n+   Implementation of sequence number generator that uses the real clock. Not\n+   thread-safe.\n+ */\n+class ClockBaseSequenceNumberGenerator : public SequenceNumberGenerator\n+{\n+public:\n+    long long get_sequence_number() override","path":"src/openlcb/IfTcpImpl.hxx","position":245,"original_position":245,"commit_id":"da41c9587027c95ea27465bc2631b6d587ea5434","original_commit_id":"da41c9587027c95ea27465bc2631b6d587ea5434","user":{"login":"bakerstu","id":2278798,"node_id":"MDQ6VXNlcjIyNzg3OTg=","avatar_url":"https://avatars0.githubusercontent.com/u/2278798?v=4","gravatar_id":"","url":"https://api.github.com/users/bakerstu","html_url":"https://github.com/bakerstu","followers_url":"https://api.github.com/users/bakerstu/followers","following_url":"https://api.github.com/users/bakerstu/following{/other_user}","gists_url":"https://api.github.com/users/bakerstu/gists{/gist_id}","starred_url":"https://api.github.com/users/bakerstu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bakerstu/subscriptions","organizations_url":"https://api.github.com/users/bakerstu/orgs","repos_url":"https://api.github.com/users/bakerstu/repos","events_url":"https://api.github.com/users/bakerstu/events{/privacy}","received_events_url":"https://api.github.com/users/bakerstu/received_events","type":"User","site_admin":false},"body":"Add method header comment.","created_at":"2018-12-01T17:12:03Z","updated_at":"2018-12-01T17:38:41Z","html_url":"https://github.com/bakerstu/openmrn/pull/169#discussion_r238069951","pull_request_url":"https://api.github.com/repos/bakerstu/openmrn/pulls/169","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/bakerstu/openmrn/pulls/comments/238069951"},"html":{"href":"https://github.com/bakerstu/openmrn/pull/169#discussion_r238069951"},"pull_request":{"href":"https://api.github.com/repos/bakerstu/openmrn/pulls/169"}}},"pull_request":{"url":"https://api.github.com/repos/bakerstu/openmrn/pulls/169","id":217473087,"node_id":"MDExOlB1bGxSZXF1ZXN0MjE3NDczMDg3","html_url":"https://github.com/bakerstu/openmrn/pull/169","diff_url":"https://github.com/bakerstu/openmrn/pull/169.diff","patch_url":"https://github.com/bakerstu/openmrn/pull/169.patch","issue_url":"https://api.github.com/repos/bakerstu/openmrn/issues/169","number":169,"state":"open","locked":false,"title":"TCP interface","user":{"login":"balazsracz","id":3719690,"node_id":"MDQ6VXNlcjM3MTk2OTA=","avatar_url":"https://avatars1.githubusercontent.com/u/3719690?v=4","gravatar_id":"","url":"https://api.github.com/users/balazsracz","html_url":"https://github.com/balazsracz","followers_url":"https://api.github.com/users/balazsracz/followers","following_url":"https://api.github.com/users/balazsracz/following{/other_user}","gists_url":"https://api.github.com/users/balazsracz/gists{/gist_id}","starred_url":"https://api.github.com/users/balazsracz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/balazsracz/subscriptions","organizations_url":"https://api.github.com/users/balazsracz/orgs","repos_url":"https://api.github.com/users/balazsracz/repos","events_url":"https://api.github.com/users/balazsracz/events{/privacy}","received_events_url":"https://api.github.com/users/balazsracz/received_events","type":"User","site_admin":false},"body":"Implements an interface using the draft Tcp protocol. \r\nThe new interface operates in conjunction with a Hub<string>, where each TCP rendered packet is in exactly one buffer traversing the Hub. A select-aware state flow is added that segments incoming bytes from an fd into packets of the Tcp protocol format with as little memory fragmentation as possible. This needed a new HubDeviceSelect (which is now templated on the read segmenting flow) and the equivalent code of create_gc_port_for_can_hub to handle graceful closing of sockets by the remote side, socket errors or teardown of the interface locally (in unit tests).\r\nThis scheme supports having multiple clients connected to a single hub, which is necessary for TCP servers. There is no filtering, i.e., all messages are passed on to every client.\r\n\r\nThe tcp interface itself converts between the Buffer<string> having the binary (wire) representation and the Buffer<GenMessage> which is the inmemory struct representation, taking care of loopback of global and addressed messages, as well as filtering incoming addressed messages.\r\n\r\nFragmented messages are not yet supported. The datagram service is not yet supported. Routing between a CAN interface and a TCP interface is not yet supported.\r\n\r\nUnit tests are added for the following schemes:\r\n- static parsing and rendering functions; sequence generator\r\n- the transmit flow communicating with an FD.\r\n- the receive flow reading from an FD using select; with various alignment cases as it is reassembling a single string from the FD.\r\n- the interface, for sending and receiving addressed and global messages, including tests for loopback and filtering noninteresting addressed messages\r\n- the interface handling of VerifyNodeID messages.\r\n- a TcpIf and a loopback socket connected as a client, sending and receiving wire payload directly on the fd.\r\n- a TcpIf as a hub, with three loopback sockets connected as clients and each having its own TcpIf instance, sending GenMessages between each other using the high-level APIs. ","created_at":"2018-09-22T19:03:33Z","updated_at":"2018-12-01T17:38:40Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4ac6041c963ca5c748d94277d5195e780d4943f7","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/bakerstu/openmrn/pulls/169/commits","review_comments_url":"https://api.github.com/repos/bakerstu/openmrn/pulls/169/comments","review_comment_url":"https://api.github.com/repos/bakerstu/openmrn/pulls/comments{/number}","comments_url":"https://api.github.com/repos/bakerstu/openmrn/issues/169/comments","statuses_url":"https://api.github.com/repos/bakerstu/openmrn/statuses/da41c9587027c95ea27465bc2631b6d587ea5434","head":{"label":"bakerstu:bracz-tcpiface","ref":"bracz-tcpiface","sha":"da41c9587027c95ea27465bc2631b6d587ea5434","user":{"login":"bakerstu","id":2278798,"node_id":"MDQ6VXNlcjIyNzg3OTg=","avatar_url":"https://avatars0.githubusercontent.com/u/2278798?v=4","gravatar_id":"","url":"https://api.github.com/users/bakerstu","html_url":"https://github.com/bakerstu","followers_url":"https://api.github.com/users/bakerstu/followers","following_url":"https://api.github.com/users/bakerstu/following{/other_user}","gists_url":"https://api.github.com/users/bakerstu/gists{/gist_id}","starred_url":"https://api.github.com/users/bakerstu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bakerstu/subscriptions","organizations_url":"https://api.github.com/users/bakerstu/orgs","repos_url":"https://api.github.com/users/bakerstu/repos","events_url":"https://api.github.com/users/bakerstu/events{/privacy}","received_events_url":"https://api.github.com/users/bakerstu/received_events","type":"User","site_admin":false},"repo":{"id":6500256,"node_id":"MDEwOlJlcG9zaXRvcnk2NTAwMjU2","name":"openmrn","full_name":"bakerstu/openmrn","private":false,"owner":{"login":"bakerstu","id":2278798,"node_id":"MDQ6VXNlcjIyNzg3OTg=","avatar_url":"https://avatars0.githubusercontent.com/u/2278798?v=4","gravatar_id":"","url":"https://api.github.com/users/bakerstu","html_url":"https://github.com/bakerstu","followers_url":"https://api.github.com/users/bakerstu/followers","following_url":"https://api.github.com/users/bakerstu/following{/other_user}","gists_url":"https://api.github.com/users/bakerstu/gists{/gist_id}","starred_url":"https://api.github.com/users/bakerstu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bakerstu/subscriptions","organizations_url":"https://api.github.com/users/bakerstu/orgs","repos_url":"https://api.github.com/users/bakerstu/repos","events_url":"https://api.github.com/users/bakerstu/events{/privacy}","received_events_url":"https://api.github.com/users/bakerstu/received_events","type":"User","site_admin":false},"html_url":"https://github.com/bakerstu/openmrn","description":"OpenMRN (Open Model Railroad Network)","fork":false,"url":"https://api.github.com/repos/bakerstu/openmrn","forks_url":"https://api.github.com/repos/bakerstu/openmrn/forks","keys_url":"https://api.github.com/repos/bakerstu/openmrn/keys{/key_id}","collaborators_url":"https://api.github.com/repos/bakerstu/openmrn/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/bakerstu/openmrn/teams","hooks_url":"https://api.github.com/repos/bakerstu/openmrn/hooks","issue_events_url":"https://api.github.com/repos/bakerstu/openmrn/issues/events{/number}","events_url":"https://api.github.com/repos/bakerstu/openmrn/events","assignees_url":"https://api.github.com/repos/bakerstu/openmrn/assignees{/user}","branches_url":"https://api.github.com/repos/bakerstu/openmrn/branches{/branch}","tags_url":"https://api.github.com/repos/bakerstu/openmrn/tags","blobs_url":"https://api.github.com/repos/bakerstu/openmrn/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/bakerstu/openmrn/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/bakerstu/openmrn/git/refs{/sha}","trees_url":"https://api.github.com/repos/bakerstu/openmrn/git/trees{/sha}","statuses_url":"https://api.github.com/repos/bakerstu/openmrn/statuses/{sha}","languages_url":"https://api.github.com/repos/bakerstu/openmrn/languages","stargazers_url":"https://api.github.com/repos/bakerstu/openmrn/stargazers","contributors_url":"https://api.github.com/repos/bakerstu/openmrn/contributors","subscribers_url":"https://api.github.com/repos/bakerstu/openmrn/subscribers","subscription_url":"https://api.github.com/repos/bakerstu/openmrn/subscription","commits_url":"https://api.github.com/repos/bakerstu/openmrn/commits{/sha}","git_commits_url":"https://api.github.com/repos/bakerstu/openmrn/git/commits{/sha}","comments_url":"https://api.github.com/repos/bakerstu/openmrn/comments{/number}","issue_comment_url":"https://api.github.com/repos/bakerstu/openmrn/issues/comments{/number}","contents_url":"https://api.github.com/repos/bakerstu/openmrn/contents/{+path}","compare_url":"https://api.github.com/repos/bakerstu/openmrn/compare/{base}...{head}","merges_url":"https://api.github.com/repos/bakerstu/openmrn/merges","archive_url":"https://api.github.com/repos/bakerstu/openmrn/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/bakerstu/openmrn/downloads","issues_url":"https://api.github.com/repos/bakerstu/openmrn/issues{/number}","pulls_url":"https://api.github.com/repos/bakerstu/openmrn/pulls{/number}","milestones_url":"https://api.github.com/repos/bakerstu/openmrn/milestones{/number}","notifications_url":"https://api.github.com/repos/bakerstu/openmrn/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/bakerstu/openmrn/labels{/name}","releases_url":"https://api.github.com/repos/bakerstu/openmrn/releases{/id}","deployments_url":"https://api.github.com/repos/bakerstu/openmrn/deployments","created_at":"2012-11-02T02:20:12Z","updated_at":"2018-11-09T18:41:33Z","pushed_at":"2018-12-01T05:37:18Z","git_url":"git://github.com/bakerstu/openmrn.git","ssh_url":"git@github.com:bakerstu/openmrn.git","clone_url":"https://github.com/bakerstu/openmrn.git","svn_url":"https://github.com/bakerstu/openmrn","homepage":null,"size":13948,"stargazers_count":12,"watchers_count":12,"language":"C++","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":14,"mirror_url":null,"archived":false,"open_issues_count":40,"license":{"key":"bsd-2-clause","name":"BSD 2-Clause \"Simplified\" License","spdx_id":"BSD-2-Clause","url":"https://api.github.com/licenses/bsd-2-clause","node_id":"MDc6TGljZW5zZTQ="},"forks":14,"open_issues":40,"watchers":12,"default_branch":"master"}},"base":{"label":"bakerstu:master","ref":"master","sha":"b05e6922743466fcb19b5357850c31b538d2ef22","user":{"login":"bakerstu","id":2278798,"node_id":"MDQ6VXNlcjIyNzg3OTg=","avatar_url":"https://avatars0.githubusercontent.com/u/2278798?v=4","gravatar_id":"","url":"https://api.github.com/users/bakerstu","html_url":"https://github.com/bakerstu","followers_url":"https://api.github.com/users/bakerstu/followers","following_url":"https://api.github.com/users/bakerstu/following{/other_user}","gists_url":"https://api.github.com/users/bakerstu/gists{/gist_id}","starred_url":"https://api.github.com/users/bakerstu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bakerstu/subscriptions","organizations_url":"https://api.github.com/users/bakerstu/orgs","repos_url":"https://api.github.com/users/bakerstu/repos","events_url":"https://api.github.com/users/bakerstu/events{/privacy}","received_events_url":"https://api.github.com/users/bakerstu/received_events","type":"User","site_admin":false},"repo":{"id":6500256,"node_id":"MDEwOlJlcG9zaXRvcnk2NTAwMjU2","name":"openmrn","full_name":"bakerstu/openmrn","private":false,"owner":{"login":"bakerstu","id":2278798,"node_id":"MDQ6VXNlcjIyNzg3OTg=","avatar_url":"https://avatars0.githubusercontent.com/u/2278798?v=4","gravatar_id":"","url":"https://api.github.com/users/bakerstu","html_url":"https://github.com/bakerstu","followers_url":"https://api.github.com/users/bakerstu/followers","following_url":"https://api.github.com/users/bakerstu/following{/other_user}","gists_url":"https://api.github.com/users/bakerstu/gists{/gist_id}","starred_url":"https://api.github.com/users/bakerstu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bakerstu/subscriptions","organizations_url":"https://api.github.com/users/bakerstu/orgs","repos_url":"https://api.github.com/users/bakerstu/repos","events_url":"https://api.github.com/users/bakerstu/events{/privacy}","received_events_url":"https://api.github.com/users/bakerstu/received_events","type":"User","site_admin":false},"html_url":"https://github.com/bakerstu/openmrn","description":"OpenMRN (Open Model Railroad Network)","fork":false,"url":"https://api.github.com/repos/bakerstu/openmrn","forks_url":"https://api.github.com/repos/bakerstu/openmrn/forks","keys_url":"https://api.github.com/repos/bakerstu/openmrn/keys{/key_id}","collaborators_url":"https://api.github.com/repos/bakerstu/openmrn/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/bakerstu/openmrn/teams","hooks_url":"https://api.github.com/repos/bakerstu/openmrn/hooks","issue_events_url":"https://api.github.com/repos/bakerstu/openmrn/issues/events{/number}","events_url":"https://api.github.com/repos/bakerstu/openmrn/events","assignees_url":"https://api.github.com/repos/bakerstu/openmrn/assignees{/user}","branches_url":"https://api.github.com/repos/bakerstu/openmrn/branches{/branch}","tags_url":"https://api.github.com/repos/bakerstu/openmrn/tags","blobs_url":"https://api.github.com/repos/bakerstu/openmrn/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/bakerstu/openmrn/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/bakerstu/openmrn/git/refs{/sha}","trees_url":"https://api.github.com/repos/bakerstu/openmrn/git/trees{/sha}","statuses_url":"https://api.github.com/repos/bakerstu/openmrn/statuses/{sha}","languages_url":"https://api.github.com/repos/bakerstu/openmrn/languages","stargazers_url":"https://api.github.com/repos/bakerstu/openmrn/stargazers","contributors_url":"https://api.github.com/repos/bakerstu/openmrn/contributors","subscribers_url":"https://api.github.com/repos/bakerstu/openmrn/subscribers","subscription_url":"https://api.github.com/repos/bakerstu/openmrn/subscription","commits_url":"https://api.github.com/repos/bakerstu/openmrn/commits{/sha}","git_commits_url":"https://api.github.com/repos/bakerstu/openmrn/git/commits{/sha}","comments_url":"https://api.github.com/repos/bakerstu/openmrn/comments{/number}","issue_comment_url":"https://api.github.com/repos/bakerstu/openmrn/issues/comments{/number}","contents_url":"https://api.github.com/repos/bakerstu/openmrn/contents/{+path}","compare_url":"https://api.github.com/repos/bakerstu/openmrn/compare/{base}...{head}","merges_url":"https://api.github.com/repos/bakerstu/openmrn/merges","archive_url":"https://api.github.com/repos/bakerstu/openmrn/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/bakerstu/openmrn/downloads","issues_url":"https://api.github.com/repos/bakerstu/openmrn/issues{/number}","pulls_url":"https://api.github.com/repos/bakerstu/openmrn/pulls{/number}","milestones_url":"https://api.github.com/repos/bakerstu/openmrn/milestones{/number}","notifications_url":"https://api.github.com/repos/bakerstu/openmrn/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/bakerstu/openmrn/labels{/name}","releases_url":"https://api.github.com/repos/bakerstu/openmrn/releases{/id}","deployments_url":"https://api.github.com/repos/bakerstu/openmrn/deployments","created_at":"2012-11-02T02:20:12Z","updated_at":"2018-11-09T18:41:33Z","pushed_at":"2018-12-01T05:37:18Z","git_url":"git://github.com/bakerstu/openmrn.git","ssh_url":"git@github.com:bakerstu/openmrn.git","clone_url":"https://github.com/bakerstu/openmrn.git","svn_url":"https://github.com/bakerstu/openmrn","homepage":null,"size":13948,"stargazers_count":12,"watchers_count":12,"language":"C++","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":14,"mirror_url":null,"archived":false,"open_issues_count":40,"license":{"key":"bsd-2-clause","name":"BSD 2-Clause \"Simplified\" License","spdx_id":"BSD-2-Clause","url":"https://api.github.com/licenses/bsd-2-clause","node_id":"MDc6TGljZW5zZTQ="},"forks":14,"open_issues":40,"watchers":12,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/bakerstu/openmrn/pulls/169"},"html":{"href":"https://github.com/bakerstu/openmrn/pull/169"},"issue":{"href":"https://api.github.com/repos/bakerstu/openmrn/issues/169"},"comments":{"href":"https://api.github.com/repos/bakerstu/openmrn/issues/169/comments"},"review_comments":{"href":"https://api.github.com/repos/bakerstu/openmrn/pulls/169/comments"},"review_comment":{"href":"https://api.github.com/repos/bakerstu/openmrn/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/bakerstu/openmrn/pulls/169/commits"},"statuses":{"href":"https://api.github.com/repos/bakerstu/openmrn/statuses/da41c9587027c95ea27465bc2631b6d587ea5434"}},"author_association":"COLLABORATOR"}}
{"actor":{"display_login":"bakerstu"}}