Ejemplo n.º 1
0
            bool ReadSingleFrame(AmfReader reader)
            {
                if (!ChunkStream.ReadFrom1(reader, out var streamId, out var opaque))
                {
                    return(false);
                }

                if (!streams.TryGetValue(streamId, out var previous))
                {
                    previous = new ChunkStream.Snapshot()
                    {
                        ChunkStreamId = streamId
                    }
                }
                ;

                if (!ChunkStream.ReadFrom2(reader, previous, opaque, out var next))
                {
                    return(false);
                }

                streams[streamId] = next;
                context.RequestReadAllocation(next.MessageLength);

                var key     = (next.ChunkStreamId, next.MessageStreamId);
                var builder = messages.TryGetValue(key, out var packet) ? packet : messages[key] = new Builder(next.MessageLength);
                var length  = Math.Min(chunkLength, builder.Remaining);

                if (!reader.HasLength(length))
                {
                    return(false);
                }

                builder.AddData(
                    reader.ReadSpan(length));

                if (builder.Current == builder.Length)
                {
                    messages.Remove(key);

                    using (builder)
                    {
                        var dereader = __readSingleFrameReader;
                        dereader.Rebind(builder.Span);

                        var message = Deserialize(next.ContentType, dereader);
                        DispatchMessage(message);
                    }
                }

                return(true);
            }

            void DispatchMessage(RtmpMessage message)
            {
                switch (message)
                {
                case ChunkLength chunk:
                    Kon.Trace("received: chunk-length", new { length = chunk.Length });

                    if (chunk.Length < 0)
                    {
                        throw new ArgumentException("invalid chunk length");
                    }

                    context.RequestReadAllocation(chunk.Length);
                    chunkLength = chunk.Length;
                    break;

                case WindowAcknowledgementSize acknowledgement:
                    if (acknowledgement.Count < 0)
                    {
                        throw new ArgumentException("invalid acknowledgement window length");
                    }

                    acknowledgementLength = acknowledgement.Count;
                    break;

                case Abort abort:
                    Kon.Trace("received: abort", new { chunk = abort.ChunkStreamId });

                    // delete the chunk stream
                    streams.Remove(abort.ChunkStreamId);

                    // then, delete all message streams associated with that chunk stream
                    foreach (var(key, builder) in messages.FilterArray(x => x.Key.chunkStreamId == abort.ChunkStreamId))
                    {
                        messages.Remove(key);
                        builder.Dispose();
                    }

                    break;

                default:
                    owner.InternalReceiveEvent(message);
                    break;
                }
            }

            // todo: refactor
            RtmpMessage Deserialize(PacketContentType contentType, AmfReader r)
            {
                // (this comment must be kept in sync at rtmpclient.reader.cs and rtmpclient.writer.cs)
                //
                // unsupported type summary:
                //
                // - aggregate:      we have never encountered this packet in the wild
                // - shared objects: we have not found a use case for this
                // - data commands:  we have not found a use case for this, though it should be extremely easy to
                //                       support. it's just a one-way equivalent of command (invoke). that is, we don't
                //                       generate an invoke id for it, and it does not contain headers. other than that,
                //                       they're identical. we can use existing logic and add if statements to surround
                //                       writing the invokeid + headers if needed.

                switch (contentType)
                {
                case PacketContentType.SetChunkSize:
                    return(new ChunkLength(
                               length: r.ReadInt32()));

                case PacketContentType.AbortMessage:
                    return(new Abort(
                               chunkStreamId: r.ReadInt32()));

                case PacketContentType.Acknowledgement:
                    return(new Acknowledgement(
                               read: r.ReadUInt32()));

                case PacketContentType.UserControlMessage:
                    var type   = r.ReadUInt16();
                    var values = EnumerableEx.Range(r.Remaining / 4, r.ReadUInt32);

                    return(new UserControlMessage(
                               type:   (UserControlMessage.Type)type,
                               values: values));

                case PacketContentType.WindowAcknowledgementSize:
                    return(new WindowAcknowledgementSize(
                               count: r.ReadInt32()));

                case PacketContentType.SetPeerBandwith:
                    return(new PeerBandwidth(
                               acknowledgementWindowSize: r.ReadInt32(),
                               type:                 r.ReadByte()));

                case PacketContentType.Audio:
                    return(new AudioData(
                               r.ReadBytes(r.Remaining)));

                case PacketContentType.Video:
                    return(new VideoData(
                               r.ReadBytes(r.Remaining)));

                case PacketContentType.DataAmf0:
                    throw NotSupportedException("data-amf0");

                case PacketContentType.SharedObjectAmf0:
                    throw NotSupportedException("sharedobject-amf0");

                case PacketContentType.CommandAmf0:
                    return(ReadCommand(ObjectEncoding.Amf0, contentType, r));

                case PacketContentType.DataAmf3:
                    throw NotSupportedException("data-amf3");

                case PacketContentType.SharedObjectAmf3:
                    throw NotSupportedException("sharedobject-amf0");

                case PacketContentType.CommandAmf3:
                    var encoding = (ObjectEncoding)r.ReadByte();
                    return(ReadCommand(encoding, contentType, r));

                case PacketContentType.Aggregate:
                    throw NotSupportedException("aggregate");

                default:
                    throw NotSupportedException($"unknown ({contentType})");
                }
            }