{"action":"created","issue":{"url":"https://api.github.com/repos/Red5/red5-server-common/issues/45","repository_url":"https://api.github.com/repos/Red5/red5-server-common","labels_url":"https://api.github.com/repos/Red5/red5-server-common/issues/45/labels{/name}","comments_url":"https://api.github.com/repos/Red5/red5-server-common/issues/45/comments","events_url":"https://api.github.com/repos/Red5/red5-server-common/issues/45/events","html_url":"https://github.com/Red5/red5-server-common/issues/45","id":168781761,"number":45,"title":"Fix for RTMPProtocolDecoder failure when media chunks cross RTMPT requests","user":{"login":"jessecrossen","id":780439,"avatar_url":"https://avatars.githubusercontent.com/u/780439?v=3","gravatar_id":"","url":"https://api.github.com/users/jessecrossen","html_url":"https://github.com/jessecrossen","followers_url":"https://api.github.com/users/jessecrossen/followers","following_url":"https://api.github.com/users/jessecrossen/following{/other_user}","gists_url":"https://api.github.com/users/jessecrossen/gists{/gist_id}","starred_url":"https://api.github.com/users/jessecrossen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jessecrossen/subscriptions","organizations_url":"https://api.github.com/users/jessecrossen/orgs","repos_url":"https://api.github.com/users/jessecrossen/repos","events_url":"https://api.github.com/users/jessecrossen/events{/privacy}","received_events_url":"https://api.github.com/users/jessecrossen/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2016-08-02T01:50:44Z","updated_at":"2016-08-02T20:48:25Z","closed_at":null,"body":"The bug can be reproduced as follows:\r\n\r\n1) Connect to Red5 using RTMPT or RTMPS:\r\n\r\n```as3\r\nvar nc:NetConnection = new NetConnection();\r\nnc.connect('rtmpt://example.com:5080/application');\r\n```\r\n\r\n2) After the connection is made, start recording video using a buffer:\r\n\r\n```as3\r\nvar ns:NetStream = new NetStream(nc);\r\nns.bufferTime(60);\r\nns.attachCamera(camera);\r\nns.attachAudio(mic);\r\nns.publish('streamid', 'record');\r\n```\r\n\r\n3) Record until there are few seconds of buffer (it helps to slow down your upload speed with `tc` or similar), then stop and wait for the buffer to drain as follows:\r\n\r\n```as3\r\n// disconnect sources to allow buffer to drain\r\nns.attachCamera(null);\r\nns.attachAudio(null);\r\n// wait for the buffer to be empty and close the stream\r\nsetInterval(function():void {\r\n  if (! (ns.bufferLength > 0.0)) {\r\n    ns.close();\r\n  }\r\n}, 500);\r\n```\r\n\r\n4) Note that Red5 does not close and finalize the stream until the connection closes.\r\n\r\nAfter examining trace output, I understand the mechanism like this:\r\n\r\nWhen the connection begins draining in step 3, it's no longer bound by real-time constraints, so chunks are sent as fast as possible. There seems to be a size limit of 8192 bytes per request, and the Flash client will fill this completely if it can. Since this is not an even multiple of common chunk sizes (e.g. 258 bytes), partial chunks appear at the ends of requests.\r\n\r\nIn the main loop of `RTMPProtocolDecoder.decodeBuffer`, an instance of `RTMPDecodeState` is used to track state. This state persists across calls, and the only place it's cleared is here:\r\n\r\n```java\r\nif (state.canStartDecoding(remaining)) {\r\n  log.trace(\"Can start decoding\");\r\n  state.startDecoding();\r\n} else {\r\n  log.trace(\"Cannot start decoding\");\r\n  break;\r\n}\r\n```\r\n\r\nNow, suppose a large buffer is decoded and goes into the buffered state on its final chunk. After the final chunk has been processed, the state is left in `RTMPDecodeState.DECODER_BUFFER` for the next chunk, since we exit the loop and `state.startDecoding()` is not called again. If the next buffer is sufficiently large, there's no problem, but suppose we get a small buffer containing the `closeStream` command, which is what happens in the reproduction method above. The `state.canStartDecoding` test fails, and we wind up waiting until more data comes in, which it won't since recording is finished, so the `closeStream` call is never parsed.\r\n\r\nI can't claim to understand this state machine well enough to suggest a perfect fix, but I've made a patch that seems to work by calling `state.startDecoding()` before entering the decode loop, like so:\r\n\r\n```java\r\n// get the local decode state\r\nRTMPDecodeState state = conn.getDecoderState();\r\nstate.startDecoding(); // <= add this line to initialize the state\r\nlog.trace(\"RTMP decode state {}\", state);\r\n```\r\n\r\nThis does seem to fix the issue without causing other problems. Since the unparsed portion of the previous buffer should be available at this point, I think the only consequence will be to re-read the chunk header at the start of the buffer, and only on the first pass through the loop. However, I haven't tested extensively enough to rule out unintended side effects, which is why I'm filing this as an issue rather than a fork and pull request."},"comment":{"url":"https://api.github.com/repos/Red5/red5-server-common/issues/comments/237038665","html_url":"https://github.com/Red5/red5-server-common/issues/45#issuecomment-237038665","issue_url":"https://api.github.com/repos/Red5/red5-server-common/issues/45","id":237038665,"user":{"login":"mondain","id":510557,"avatar_url":"https://avatars.githubusercontent.com/u/510557?v=3","gravatar_id":"","url":"https://api.github.com/users/mondain","html_url":"https://github.com/mondain","followers_url":"https://api.github.com/users/mondain/followers","following_url":"https://api.github.com/users/mondain/following{/other_user}","gists_url":"https://api.github.com/users/mondain/gists{/gist_id}","starred_url":"https://api.github.com/users/mondain/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mondain/subscriptions","organizations_url":"https://api.github.com/users/mondain/orgs","repos_url":"https://api.github.com/users/mondain/repos","events_url":"https://api.github.com/users/mondain/events{/privacy}","received_events_url":"https://api.github.com/users/mondain/received_events","type":"User","site_admin":false},"created_at":"2016-08-02T20:48:25Z","updated_at":"2016-08-02T20:48:25Z","body":"Thanks for digging into this, we await your final analysis."}}
{"actor":{"display_login":"mondain"}}