Passed
Pull Request — master (#161)
by
unknown
12:07
created

UdpTransport::sendMessageInChunks()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 35
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3.0067

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 3
eloc 16
c 2
b 1
f 0
nc 3
nop 1
dl 0
loc 35
ccs 10
cts 11
cp 0.9091
crap 3.0067
rs 9.7333
1
<?php
2
declare(strict_types=1);
3
4
/*
5
 * This file is part of the php-gelf package.
6
 *
7
 * (c) Benjamin Zikarsky <http://benjamin-zikarsky.de>
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12
13
namespace Gelf\Transport;
14
15
use Gelf\MessageInterface as Message;
16
use Gelf\Encoder\CompressedJsonEncoder as DefaultEncoder;
17
use InvalidArgumentException;
18
use LogicException;
19
use RuntimeException;
20
21
/**
22
 * UdpTransport allows the transfer of GELF-messages to an compatible
23
 * GELF-UDP-backend as described in
24
 * https://github.com/Graylog2/graylog2-docs/wiki/GELF
25
 *
26
 * It can also act as a direct publisher
27
 *
28
 * @author Benjamin Zikarsky <[email protected]>
29
 */
30
class UdpTransport extends AbstractTransport
31
{
32
    private const CHUNK_GELF_ID = "\x1e\x0f";
33
    private const CHUNK_MAX_COUNT = 128; // as per GELF spec
34
    private const CHUNK_HEADER_LENGTH = 12; // GELF ID (2b), id (8b) , sequence (2b)
35
    
36
    public const CHUNK_SIZE_LAN = 8154;
37
    public const CHUNK_SIZE_WAN = 1420;
38
39
    private const DEFAULT_HOST = "127.0.0.1";
40
    private const DEFAULT_PORT = 12201;
41
    
42
    private StreamSocketClient $socketClient;
43
44
    /**
45
     * Class constructor
46
     *
47
     * @param string $host when NULL or empty DEFAULT_HOST is used
48
     * @param int $port when NULL or empty DEFAULT_PORT is used
49
     * @param int $chunkSize defaults to CHUNK_SIZE_WAN,
50
     *                          0 disables chunks completely
51
     */
52
    public function __construct(
53
        string $host = self::DEFAULT_HOST,
54
        int $port = self::DEFAULT_PORT,
55
        private int $chunkSize = self::CHUNK_SIZE_WAN
56
    ) {
57 5
        parent::__construct();
58
59
        // allow NULL-like values for fallback on default
60
        $host = $host ?: self::DEFAULT_HOST;
61
        $port = $port ?: self::DEFAULT_PORT;
62
63 5
        $this->socketClient = new StreamSocketClient('udp', $host, $port);
64 5
65
        if ($chunkSize > 0 && $chunkSize <= self::CHUNK_HEADER_LENGTH) {
66 5
            throw new InvalidArgumentException('Chunk-size has to exceed ' . self::CHUNK_HEADER_LENGTH
67 5
                . ' which is the number of bytes reserved for the chunk-header');
68
        }
69 5
    }
70
71 5
    /**
72
     * @inheritDoc
73
     */
74
    public function send(Message $message): int
75 5
    {
76
        $rawMessage = $this->getMessageEncoder()->encode($message);
77
78
        // test if we need to split the message to multiple chunks
79
        // chunkSize == 0 allows for an unlimited packet-size, and therefore
80
        // disables chunking
81
        if ($this->chunkSize && strlen($rawMessage) > $this->chunkSize) {
82
            return $this->sendMessageInChunks($rawMessage);
83
        }
84 3
85
        // send message in one packet
86 3
        $this->socketClient->write($rawMessage);
87
88
        return 1;
89
    }
90
91 3
    /**
92 2
     * @deprecated use setConnectTimeoutInMs instead
93
     * @see TcpTransport::setConnectTimeout
94
     */
95
    public function setConnectTimeout(int $timeout): void
96 1
    {
97
        $this->socketClient->setConnectTimeout($timeout);
98 1
    }
99
    
100
    public function setConnectTimeoutInMs(int $timeoutInMs): void
101
    {
102
        // currently socketClient only supports int
103
        $this->socketClient->setConnectTimeout((int)ceil($timeoutInMs / 1_000));
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_STRING, expecting ',' or ')' on line 103 at column 73
Loading history...
104
    }
105
106
    /**
107
     * Sends given string in multiple chunks
108
     */
109
    private function sendMessageInChunks(string $rawMessage): int
110 2
    {
111
        /** @var int<1, max> $length */
112
        $length = $this->chunkSize - self::CHUNK_HEADER_LENGTH;
113 2
114 2
        // split to chunks
115
        $chunks = str_split($rawMessage, $length);
116 2
117 1
        $numChunks = count($chunks);
118
119 1
        if ($numChunks > self::CHUNK_MAX_COUNT) {
120 1
            throw new RuntimeException(
121
                sprintf(
122
                    "Message is too big. Chunk count exceeds %d",
123
                    self::CHUNK_MAX_COUNT
124
                )
125
            );
126 1
        }
127
128
        // generate a random 8byte-message-id
129
        $messageId = substr(md5(uniqid("", true), true), 0, 8);
130 1
131 1
        // send chunks with a correct chunk-header
132 1
        // @link http://graylog2.org/gelf#specs
133 1
        foreach ($chunks as $idx => $chunk) {
134 1
            $data = self::CHUNK_GELF_ID            // GELF chunk magic bytes
135
                . $messageId                       // unique message id
136
                . pack('CC', $idx, $numChunks)     // sequence information
137 1
                . $chunk                           // chunk-data
138
            ;
139
140 1
            $this->socketClient->write($data);
141
        }
142
143
        return $numChunks;
144
    }
145
}
146