/** * Consumes AMQP frame from buffer. * * Returns NULL if there are not enough data to construct whole frame. * * @param Buffer $buffer * @return AbstractFrame */ public function consumeFrame(Buffer $buffer) { // not enough data if ($buffer->getLength() < 7) { return null; } $type = $buffer->readUint8(0); $channel = $buffer->readUint16(1); $payloadSize = $buffer->readUint32(3); $payloadOffset = 7; // type:uint8=>1 + channel:uint16=>2 + payloadSize:uint32=>4 ==> 7 // not enough data if ($buffer->getLength() < $payloadOffset + $payloadSize + 1) { return null; } $buffer->consume(7); $payload = $buffer->consume($payloadSize); $frameEnd = $buffer->consumeUint8(); if ($frameEnd !== Constants::FRAME_END) { throw new ProtocolException(sprintf("Frame end byte invalid - expected 0x%02x, got 0x%02x.", Constants::FRAME_END, $frameEnd)); } $frameBuffer = new Buffer($payload); if ($type === Constants::FRAME_METHOD) { $frame = $this->consumeMethodFrame($frameBuffer); } elseif ($type === Constants::FRAME_HEADER) { // see https://github.com/pika/pika/blob/master/pika/spec.py class BasicProperties $frame = new ContentHeaderFrame(); $frame->classId = $frameBuffer->consumeUint16(); $frame->weight = $frameBuffer->consumeUint16(); $frame->bodySize = $frameBuffer->consumeUint64(); $frame->flags = $flags = $frameBuffer->consumeUint16(); if ($flags & ContentHeaderFrame::FLAG_CONTENT_TYPE) { $frame->contentType = $frameBuffer->consume($frameBuffer->consumeUint8()); } if ($flags & ContentHeaderFrame::FLAG_CONTENT_ENCODING) { $frame->contentEncoding = $frameBuffer->consume($frameBuffer->consumeUint8()); } if ($flags & ContentHeaderFrame::FLAG_HEADERS) { $frame->headers = $this->consumeTable($frameBuffer); } if ($flags & ContentHeaderFrame::FLAG_DELIVERY_MODE) { $frame->deliveryMode = $frameBuffer->consumeUint8(); } if ($flags & ContentHeaderFrame::FLAG_PRIORITY) { $frame->priority = $frameBuffer->consumeUint8(); } if ($flags & ContentHeaderFrame::FLAG_CORRELATION_ID) { $frame->correlationId = $frameBuffer->consume($frameBuffer->consumeUint8()); } if ($flags & ContentHeaderFrame::FLAG_REPLY_TO) { $frame->replyTo = $frameBuffer->consume($frameBuffer->consumeUint8()); } if ($flags & ContentHeaderFrame::FLAG_EXPIRATION) { $frame->expiration = $frameBuffer->consume($frameBuffer->consumeUint8()); } if ($flags & ContentHeaderFrame::FLAG_MESSAGE_ID) { $frame->messageId = $frameBuffer->consume($frameBuffer->consumeUint8()); } if ($flags & ContentHeaderFrame::FLAG_TIMESTAMP) { $frame->timestamp = $this->consumeTimestamp($frameBuffer); } if ($flags & ContentHeaderFrame::FLAG_TYPE) { $frame->typeHeader = $frameBuffer->consume($frameBuffer->consumeUint8()); } if ($flags & ContentHeaderFrame::FLAG_USER_ID) { $frame->userId = $frameBuffer->consume($frameBuffer->consumeUint8()); } if ($flags & ContentHeaderFrame::FLAG_APP_ID) { $frame->appId = $frameBuffer->consume($frameBuffer->consumeUint8()); } if ($flags & ContentHeaderFrame::FLAG_CLUSTER_ID) { $frame->clusterId = $frameBuffer->consume($frameBuffer->consumeUint8()); } } elseif ($type === Constants::FRAME_BODY) { $frame = new ContentBodyFrame(); $frame->payload = $frameBuffer->consume($frameBuffer->getLength()); } elseif ($type === Constants::FRAME_HEARTBEAT) { $frame = new HeartbeatFrame(); if (!$frameBuffer->isEmpty()) { throw new ProtocolException("Heartbeat frame must be empty."); } } else { throw new ProtocolException("Unhandled frame type '{$type}'."); } if (!$frameBuffer->isEmpty()) { throw new ProtocolException("Frame buffer not entirely consumed."); } /** @var AbstractFrame $frame */ $frame->type = $type; $frame->channel = $channel; $frame->payloadSize = $payloadSize; // DO NOT CALL! ContentBodyFrame uses payload for body // $frame->setPayload($payload); return $frame; }