Completed
Pull Request — master (#74)
by Scott
02:26
created

UdpTransport   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 115
Duplicated Lines 12.17 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 96.97%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 10
c 1
b 0
f 0
lcom 1
cbo 5
dl 14
loc 115
ccs 32
cts 33
cp 0.9697
rs 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 14 14 3
A send() 0 16 3
B sendMessageInChunks() 0 38 4

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
/*
4
 * This file is part of the php-gelf package.
5
 *
6
 * (c) Benjamin Zikarsky <http://benjamin-zikarsky.de>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Gelf\Transport;
13
14
use Gelf\MessageInterface as Message;
15
use Gelf\Encoder\CompressedJsonEncoder as DefaultEncoder;
16
use ParagonIE\ConstantTime\Binary;
17
use RuntimeException;
18
19
/**
20
 * UdpTransport allows the transfer of GELF-messages to an compatible
21
 * GELF-UDP-backend as described in
22
 * https://github.com/Graylog2/graylog2-docs/wiki/GELF
23
 *
24
 * It can also act as a direct publisher
25
 *
26
 * @author Benjamin Zikarsky <[email protected]>
27
 */
28
class UdpTransport extends AbstractTransport
29
{
30
    const CHUNK_GELF_ID = "\x1e\x0f";
31
    const CHUNK_MAX_COUNT = 128; // as per GELF spec
32
    const CHUNK_SIZE_LAN = 8154;
33
    const CHUNK_SIZE_WAN = 1420;
34
35
    const DEFAULT_HOST = "127.0.0.1";
36
    const DEFAULT_PORT = 12201;
37
38
    /**
39
     * @var int
40
     */
41
    protected $chunkSize;
42
43
    /**
44
     * @var StreamSocketClient
45
     */
46
    protected $socketClient;
47
48
    /**
49
     * Class constructor
50
     *
51
     * @param string $host      when NULL or empty DEFAULT_HOST is used
52
     * @param int    $port      when NULL or empty DEFAULT_PORT is used
53
     * @param int    $chunkSize defaults to CHUNK_SIZE_WAN,
54
     *                          0 disables chunks completely
55
     */
56 5 View Code Duplication
    public function __construct(
57
        $host = self::DEFAULT_HOST,
58
        $port = self::DEFAULT_PORT,
59
        $chunkSize = self::CHUNK_SIZE_WAN
60
    ) {
61
        // allow NULL-like values for fallback on default
62 5
        $host = $host ?: self::DEFAULT_HOST;
63 5
        $port = $port ?: self::DEFAULT_PORT;
64
65 5
        $this->socketClient = new StreamSocketClient('udp', $host, $port);
66 5
        $this->chunkSize = $chunkSize;
67
68 5
        $this->messageEncoder = new DefaultEncoder();
69 5
    }
70
71
    /**
72
     * Sends a Message over this transport
73
     *
74
     * @param Message $message
75
     *
76
     * @return int the number of UDP packets sent
77
     */
78 3
    public function send(Message $message)
79
    {
80 3
        $rawMessage = $this->getMessageEncoder()->encode($message);
81
82
        // test if we need to split the message to multiple chunks
83
        // chunkSize == 0 allows for an unlimited packet-size, and therefore
84
        // disables chunking
85 3
        if ($this->chunkSize && Binary::safeStrlen($rawMessage) > $this->chunkSize) {
86 2
            return $this->sendMessageInChunks($rawMessage);
87
        }
88
89
        // send message in one packet
90 1
        $this->socketClient->write($rawMessage);
91
92 1
        return 1;
93
    }
94
95
    /**
96
     * Sends given string in multiple chunks
97
     *
98
     * @param  string $rawMessage
99
     * @return int
100
     *
101
     * @throws RuntimeException on too large messages which would exceed the
102
                                maximum number of possible chunks
103
     */
104 2
    protected function sendMessageInChunks($rawMessage)
105
    {
106
        // split to chunks
107 2
        $chunks = str_split($rawMessage, $this->chunkSize);
108 2
        $numChunks = count($chunks);
109
110 2
        if ($numChunks > self::CHUNK_MAX_COUNT) {
111 1
            throw new RuntimeException(
112 1
                sprintf(
113 1
                    "Message is too big. Chunk count exceeds %d",
114
                    self::CHUNK_MAX_COUNT
115 1
                )
116 1
            );
117
        }
118
119
        // generate a random 8byte-message-id
120
        try {
121
            // Attempt to use a CSPRNG (native or random_compat) first:
122 1
            $messageId = random_bytes(8);
123 1
        } catch (\Exception $ex) {
124
            // If it can't be used safely, fall back to the old method:
125
            $messageId = substr(md5(uniqid(), true), 0, 8);
126
        }
127
128
        // send chunks with a correct chunk-header
129
        // @link http://graylog2.org/gelf#specs
130 1
        foreach ($chunks as $idx => $chunk) {
131
            $data = self::CHUNK_GELF_ID            // GELF chunk magic bytes
132 1
                . $messageId                       // unique message id
133 1
                . pack('CC', $idx, $numChunks)     // sequence information
134 1
                . $chunk                           // chunk-data
135 1
            ;
136
137 1
            $this->socketClient->write($data);
138 1
        }
139
140 1
        return $numChunks;
141
    }
142
}
143