socket-netty 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pynetty/__init__.py ADDED
@@ -0,0 +1,137 @@
1
+ """
2
+ pynetty: an event-driven asynchronous I/O library inspired by Netty
3
+ (Java), built on top of asyncio.
4
+
5
+ Typical usage (server):
6
+
7
+ from pynetty import ServerBootstrap, ChannelInboundHandler
8
+
9
+ class EchoHandler(ChannelInboundHandler):
10
+ async def channel_read(self, ctx, msg):
11
+ await ctx.write(msg) # echo
12
+
13
+ async def main():
14
+ def init_channel(channel):
15
+ channel.pipeline.add_last("echo", EchoHandler())
16
+
17
+ server = await ServerBootstrap().child_handler(init_channel).bind("0.0.0.0", 9000)
18
+ await server.serve_forever()
19
+ """
20
+
21
+ from pynetty.buffer import (
22
+ ByteBuf,
23
+ IndexOutOfBoundsError,
24
+ ByteBufAllocator,
25
+ UnpooledByteBufAllocator,
26
+ PooledByteBufAllocator,
27
+ DEFAULT_UNPOOLED,
28
+ DEFAULT_POOLED,
29
+ )
30
+ from pynetty.channel import (
31
+ Channel,
32
+ ChannelPipeline,
33
+ DuplicateHandlerNameError,
34
+ ChannelFuture,
35
+ ChannelPromise,
36
+ EventLoop,
37
+ EventLoopGroup,
38
+ ChannelOption,
39
+ apply_socket_options,
40
+ WriteBufferWaterMark,
41
+ ChannelExecutor,
42
+ FlowControlInboundHandler,
43
+ DatagramChannel,
44
+ DatagramBootstrap,
45
+ )
46
+ from pynetty.handler import (
47
+ ChannelHandler,
48
+ ChannelInboundHandler,
49
+ ChannelOutboundHandler,
50
+ ChannelInboundHandlerAdapter,
51
+ ChannelOutboundHandlerAdapter,
52
+ SimpleChannelInboundHandler,
53
+ ChannelHandlerContext,
54
+ LengthFieldBasedFrameDecoder,
55
+ LengthFieldPrepender,
56
+ ByteToMessageCodec,
57
+ ProtolibCodec,
58
+ SslContextBuilder,
59
+ IdleState,
60
+ IdleStateEvent,
61
+ IdleStateHandler,
62
+ ReadTimeoutHandler,
63
+ ReadTimeoutError,
64
+ WriteTimeoutHandler,
65
+ WriteTimeoutError,
66
+ )
67
+ from pynetty.bootstrap import ServerBootstrap, Bootstrap
68
+ from pynetty.exceptions import (
69
+ NettyException,
70
+ ChannelException,
71
+ CodecException,
72
+ DecoderException,
73
+ EncoderException,
74
+ CorruptedFrameException,
75
+ TooLongFrameException,
76
+ TimeoutException,
77
+ )
78
+
79
+ __version__ = "0.3.0"
80
+
81
+ __all__ = [
82
+ # exceptions
83
+ "NettyException",
84
+ "ChannelException",
85
+ "CodecException",
86
+ "DecoderException",
87
+ "EncoderException",
88
+ "CorruptedFrameException",
89
+ "TooLongFrameException",
90
+ "TimeoutException",
91
+ # buffer
92
+ "ByteBuf",
93
+ "IndexOutOfBoundsError",
94
+ "ByteBufAllocator",
95
+ "UnpooledByteBufAllocator",
96
+ "PooledByteBufAllocator",
97
+ "DEFAULT_UNPOOLED",
98
+ "DEFAULT_POOLED",
99
+ # channel
100
+ "Channel",
101
+ "ChannelPipeline",
102
+ "DuplicateHandlerNameError",
103
+ "ChannelFuture",
104
+ "ChannelPromise",
105
+ "EventLoop",
106
+ "EventLoopGroup",
107
+ "ChannelOption",
108
+ "apply_socket_options",
109
+ "WriteBufferWaterMark",
110
+ "ChannelExecutor",
111
+ "FlowControlInboundHandler",
112
+ "DatagramChannel",
113
+ "DatagramBootstrap",
114
+ # handler
115
+ "ChannelHandler",
116
+ "ChannelInboundHandler",
117
+ "ChannelOutboundHandler",
118
+ "ChannelInboundHandlerAdapter",
119
+ "ChannelOutboundHandlerAdapter",
120
+ "SimpleChannelInboundHandler",
121
+ "ChannelHandlerContext",
122
+ "LengthFieldBasedFrameDecoder",
123
+ "LengthFieldPrepender",
124
+ "ByteToMessageCodec",
125
+ "ProtolibCodec",
126
+ "SslContextBuilder",
127
+ "IdleState",
128
+ "IdleStateEvent",
129
+ "IdleStateHandler",
130
+ "ReadTimeoutHandler",
131
+ "ReadTimeoutError",
132
+ "WriteTimeoutHandler",
133
+ "WriteTimeoutError",
134
+ # bootstrap
135
+ "ServerBootstrap",
136
+ "Bootstrap",
137
+ ]
@@ -0,0 +1,3 @@
1
+ from pynetty.bootstrap.bootstrap import ServerBootstrap, Bootstrap
2
+
3
+ __all__ = ["ServerBootstrap", "Bootstrap"]
@@ -0,0 +1,162 @@
1
+ """
2
+ ServerBootstrap / Bootstrap: high-level entry points for standing up a
3
+ server or client, Netty-style. They configure the ChannelInitializer
4
+ and start the asyncio loop underneath. They support .option(...)
5
+ (socket options), .ssl(...) (TLS), .group(...) (EventLoopGroup), and
6
+ .water_mark(...) (backpressure).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import ssl as ssl_module
13
+ from typing import Any, Callable, Dict, Optional
14
+
15
+ from pynetty.channel.channel import Channel
16
+ from pynetty.channel.protocol_adapter import _ChannelProtocol
17
+ from pynetty.channel.channel_option import ChannelOption, apply_socket_options
18
+ from pynetty.channel.flow_control import WriteBufferWaterMark
19
+ from pynetty.channel.event_loop import EventLoopGroup
20
+ from pynetty.exceptions import ChannelException
21
+
22
+
23
+ ChannelInitializer = Callable[[Channel], "asyncio.Future | None"]
24
+
25
+
26
+ class ServerBootstrap:
27
+ """Equivalent to io.netty.bootstrap.ServerBootstrap."""
28
+
29
+ def __init__(self) -> None:
30
+ self._child_initializer: Optional[ChannelInitializer] = None
31
+ self._server: Optional[asyncio.AbstractServer] = None
32
+ self._options: Dict[str, Any] = {}
33
+ self._ssl_context: Optional[ssl_module.SSLContext] = None
34
+ self._water_mark: Optional[WriteBufferWaterMark] = None
35
+ self._group: Optional[EventLoopGroup] = None
36
+
37
+ def child_handler(self, initializer: ChannelInitializer) -> "ServerBootstrap":
38
+ self._child_initializer = initializer
39
+ return self
40
+
41
+ def option(self, name: str, value: Any) -> "ServerBootstrap":
42
+ self._options[name] = value
43
+ return self
44
+
45
+ def ssl(self, context: ssl_module.SSLContext) -> "ServerBootstrap":
46
+ self._ssl_context = context
47
+ return self
48
+
49
+ def water_mark(self, mark: WriteBufferWaterMark) -> "ServerBootstrap":
50
+ self._water_mark = mark
51
+ return self
52
+
53
+ def group(self, event_loop_group: EventLoopGroup) -> "ServerBootstrap":
54
+ self._group = event_loop_group
55
+ return self
56
+
57
+ async def bind(self, host: str = "0.0.0.0", port: int = 0) -> "ServerBootstrap":
58
+ if self._child_initializer is None:
59
+ raise ChannelException("You must call child_handler(...) before bind()")
60
+
61
+ loop = asyncio.get_event_loop()
62
+
63
+ def factory() -> Channel:
64
+ channel = Channel()
65
+ result = self._child_initializer(channel)
66
+ if asyncio.iscoroutine(result):
67
+ loop.create_task(result)
68
+ return channel
69
+
70
+ backlog = self._options.get(ChannelOption.SO_BACKLOG, 100)
71
+
72
+ server = await loop.create_server(
73
+ lambda: _ChannelProtocol(factory, water_mark=self._water_mark),
74
+ host=host,
75
+ port=port,
76
+ backlog=backlog,
77
+ ssl=self._ssl_context,
78
+ )
79
+
80
+ remaining = {k: v for k, v in self._options.items() if k != ChannelOption.SO_BACKLOG}
81
+ if remaining:
82
+ for sock in server.sockets:
83
+ apply_socket_options(sock, remaining)
84
+
85
+ self._server = server
86
+ return self
87
+
88
+ def sockets_address(self) -> list:
89
+ if self._server is None:
90
+ return []
91
+ return [sock.getsockname() for sock in self._server.sockets]
92
+
93
+ async def close(self) -> None:
94
+ if self._server is not None:
95
+ self._server.close()
96
+ await self._server.wait_closed()
97
+
98
+ async def serve_forever(self) -> None:
99
+ if self._server is None:
100
+ raise ChannelException("Call bind() before serve_forever()")
101
+ async with self._server:
102
+ await self._server.serve_forever()
103
+
104
+
105
+ class Bootstrap:
106
+ """Equivalent to io.netty.bootstrap.Bootstrap (client side)."""
107
+
108
+ def __init__(self) -> None:
109
+ self._handler_initializer: Optional[ChannelInitializer] = None
110
+ self._options: Dict[str, Any] = {}
111
+ self._ssl_context: Optional[ssl_module.SSLContext] = None
112
+ self._water_mark: Optional[WriteBufferWaterMark] = None
113
+ self._group: Optional[EventLoopGroup] = None
114
+
115
+ def handler(self, initializer: ChannelInitializer) -> "Bootstrap":
116
+ self._handler_initializer = initializer
117
+ return self
118
+
119
+ def option(self, name: str, value: Any) -> "Bootstrap":
120
+ self._options[name] = value
121
+ return self
122
+
123
+ def ssl(self, context: ssl_module.SSLContext) -> "Bootstrap":
124
+ self._ssl_context = context
125
+ return self
126
+
127
+ def water_mark(self, mark: WriteBufferWaterMark) -> "Bootstrap":
128
+ self._water_mark = mark
129
+ return self
130
+
131
+ def group(self, event_loop_group: EventLoopGroup) -> "Bootstrap":
132
+ self._group = event_loop_group
133
+ return self
134
+
135
+ async def connect(self, host: str, port: int) -> Channel:
136
+ if self._handler_initializer is None:
137
+ raise ChannelException("You must call handler(...) before connect()")
138
+
139
+ loop = asyncio.get_event_loop()
140
+ holder: dict = {}
141
+
142
+ def factory() -> Channel:
143
+ channel = Channel()
144
+ holder["channel"] = channel
145
+ result = self._handler_initializer(channel)
146
+ if asyncio.iscoroutine(result):
147
+ loop.create_task(result)
148
+ return channel
149
+
150
+ transport, protocol = await loop.create_connection(
151
+ lambda: _ChannelProtocol(factory, water_mark=self._water_mark),
152
+ host=host,
153
+ port=port,
154
+ ssl=self._ssl_context,
155
+ )
156
+
157
+ if self._options:
158
+ sock = transport.get_extra_info("socket")
159
+ if sock is not None:
160
+ apply_socket_options(sock, self._options)
161
+
162
+ return holder["channel"]
@@ -0,0 +1,18 @@
1
+ from pynetty.buffer.bytebuf import ByteBuf, IndexOutOfBoundsError
2
+ from pynetty.buffer.allocator import (
3
+ ByteBufAllocator,
4
+ UnpooledByteBufAllocator,
5
+ PooledByteBufAllocator,
6
+ DEFAULT_UNPOOLED,
7
+ DEFAULT_POOLED,
8
+ )
9
+
10
+ __all__ = [
11
+ "ByteBuf",
12
+ "IndexOutOfBoundsError",
13
+ "ByteBufAllocator",
14
+ "UnpooledByteBufAllocator",
15
+ "PooledByteBufAllocator",
16
+ "DEFAULT_UNPOOLED",
17
+ "DEFAULT_POOLED",
18
+ ]
@@ -0,0 +1,113 @@
1
+ """
2
+ ByteBufAllocator: equivalent to io.netty.buffer.ByteBufAllocator.
3
+
4
+ Netty uses this to avoid GC pressure on high-throughput hot paths
5
+ (thousands of packets/sec) by recycling buffers instead of allocating a
6
+ new one per message. Python's GC differs (refcounting + generational),
7
+ but the pattern still pays off: it avoids constantly reallocating and
8
+ resetting bytearrays in high-traffic game servers.
9
+
10
+ Two implementations, mirroring Netty:
11
+ - UnpooledByteBufAllocator: allocates a fresh ByteBuf every time
12
+ (simple, safe default).
13
+ - PooledByteBufAllocator: keeps a pool of recycled ByteBuf objects per
14
+ size bucket; release() returns them to the pool instead of discarding.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import threading
20
+ from collections import defaultdict
21
+ from typing import Dict, List
22
+
23
+ from pynetty.buffer.bytebuf import ByteBuf
24
+
25
+
26
+ class ByteBufAllocator:
27
+ """Base interface — equivalent to Netty's ByteBufAllocator interface."""
28
+
29
+ def buffer(self, initial_capacity: int = 256) -> ByteBuf:
30
+ raise NotImplementedError
31
+
32
+ def release(self, buf: ByteBuf) -> None:
33
+ raise NotImplementedError
34
+
35
+
36
+ class UnpooledByteBufAllocator(ByteBufAllocator):
37
+ """Simple allocator: every buffer() call creates a fresh ByteBuf; release() is a no-op beyond clearing."""
38
+
39
+ def buffer(self, initial_capacity: int = 256) -> ByteBuf:
40
+ return ByteBuf()
41
+
42
+ def release(self, buf: ByteBuf) -> None:
43
+ buf.clear()
44
+
45
+
46
+ class PooledByteBufAllocator(ByteBufAllocator):
47
+ """
48
+ Allocator with a real pool bucketed by size (powers of 2, same
49
+ approach Netty uses). buffer() reuses a clean ByteBuf from the
50
+ matching bucket if one is available; otherwise it creates one.
51
+
52
+ release(buf) clears it and returns it to its bucket's pool, ready
53
+ for the next buffer(similar initial_capacity) call.
54
+ """
55
+
56
+ _MIN_BUCKET = 64 # minimum bucket capacity, in bytes
57
+
58
+ def __init__(self, max_pooled_per_bucket: int = 64) -> None:
59
+ self._pools: Dict[int, List[ByteBuf]] = defaultdict(list)
60
+ self._max_pooled_per_bucket = max_pooled_per_bucket
61
+ self._lock = threading.Lock()
62
+ # Simple metrics, useful for tuning game servers
63
+ self.stats_allocated = 0
64
+ self.stats_reused = 0
65
+ self.stats_released = 0
66
+
67
+ def _bucket_for(self, capacity: int) -> int:
68
+ bucket = self._MIN_BUCKET
69
+ while bucket < capacity:
70
+ bucket *= 2
71
+ return bucket
72
+
73
+ def buffer(self, initial_capacity: int = 256) -> ByteBuf:
74
+ bucket = self._bucket_for(initial_capacity)
75
+ with self._lock:
76
+ pool = self._pools[bucket]
77
+ if pool:
78
+ buf = pool.pop()
79
+ self.stats_reused += 1
80
+ buf._pool_bucket = bucket # type: ignore[attr-defined]
81
+ return buf
82
+ self.stats_allocated += 1
83
+ buf = ByteBuf()
84
+ buf._pool_bucket = bucket # type: ignore[attr-defined]
85
+ return buf
86
+
87
+ def release(self, buf: ByteBuf) -> None:
88
+ # Use the bucket it was handed out with by buffer(); if the
89
+ # ByteBuf didn't come from this allocator (unmarked), fall back
90
+ # to the minimum bucket as a safe default.
91
+ bucket = getattr(buf, "_pool_bucket", None) or self._MIN_BUCKET
92
+ buf.clear()
93
+ with self._lock:
94
+ pool = self._pools[bucket]
95
+ if len(pool) < self._max_pooled_per_bucket:
96
+ pool.append(buf)
97
+ self.stats_released += 1
98
+
99
+ def pooled_count(self) -> int:
100
+ with self._lock:
101
+ return sum(len(p) for p in self._pools.values())
102
+
103
+ def __repr__(self) -> str:
104
+ return (
105
+ f"PooledByteBufAllocator(allocated={self.stats_allocated}, "
106
+ f"reused={self.stats_reused}, released={self.stats_released}, "
107
+ f"pooled_now={self.pooled_count()})"
108
+ )
109
+
110
+
111
+ # Default instances, equivalent to Netty's ByteBufAllocator.DEFAULT
112
+ DEFAULT_UNPOOLED = UnpooledByteBufAllocator()
113
+ DEFAULT_POOLED = PooledByteBufAllocator()
@@ -0,0 +1,256 @@
1
+ """
2
+ ByteBuf: Netty-style byte buffer.
3
+
4
+ Maintains independent reader/writer indices (unlike BytesIO), supports
5
+ dynamic growth, and exposes helpers for big-endian primitives plus
6
+ protobuf/Minecraft-style varints (heavily used in game protocols).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import struct
12
+ from typing import Union
13
+
14
+ from pynetty.exceptions import IndexOutOfBoundsError, CorruptedFrameException
15
+
16
+ __all__ = ["ByteBuf", "IndexOutOfBoundsError"]
17
+
18
+
19
+ class ByteBuf:
20
+ __slots__ = ("_data", "_reader_index", "_writer_index", "_mark", "_pool_bucket")
21
+
22
+ def __init__(self, initial: Union[bytes, bytearray, None] = None) -> None:
23
+ self._data = bytearray(initial) if initial is not None else bytearray()
24
+ self._reader_index = 0
25
+ self._writer_index = len(self._data)
26
+ self._mark = 0
27
+ self._pool_bucket = None
28
+
29
+ # ------------------------------------------------------------------
30
+ # Constructors
31
+ # ------------------------------------------------------------------
32
+ @classmethod
33
+ def buffer(cls, capacity: int = 256) -> "ByteBuf":
34
+ buf = cls()
35
+ buf._data = bytearray()
36
+ return buf
37
+
38
+ @classmethod
39
+ def wrapped(cls, data: Union[bytes, bytearray]) -> "ByteBuf":
40
+ """Wraps existing data; writer_index is left at the end (ready to read)."""
41
+ return cls(data)
42
+
43
+ # ------------------------------------------------------------------
44
+ # Indices
45
+ # ------------------------------------------------------------------
46
+ @property
47
+ def reader_index(self) -> int:
48
+ return self._reader_index
49
+
50
+ @property
51
+ def writer_index(self) -> int:
52
+ return self._writer_index
53
+
54
+ def readable_bytes(self) -> int:
55
+ return self._writer_index - self._reader_index
56
+
57
+ def is_readable(self, size: int = 1) -> bool:
58
+ return self.readable_bytes() >= size
59
+
60
+ def reset_reader_index(self) -> "ByteBuf":
61
+ self._reader_index = 0
62
+ return self
63
+
64
+ def mark_reader_index(self) -> "ByteBuf":
65
+ self._mark = self._reader_index
66
+ return self
67
+
68
+ def reset_to_mark(self) -> "ByteBuf":
69
+ self._reader_index = self._mark
70
+ return self
71
+
72
+ def skip_bytes(self, length: int) -> "ByteBuf":
73
+ self._check_readable(length)
74
+ self._reader_index += length
75
+ return self
76
+
77
+ def clear(self) -> "ByteBuf":
78
+ self._reader_index = 0
79
+ self._writer_index = 0
80
+ self._data = bytearray()
81
+ return self
82
+
83
+ # ------------------------------------------------------------------
84
+ # Internal
85
+ # ------------------------------------------------------------------
86
+ def _check_readable(self, length: int) -> None:
87
+ if self.readable_bytes() < length:
88
+ raise IndexOutOfBoundsError(
89
+ f"readerIndex({self._reader_index}) + length({length}) exceeds "
90
+ f"writerIndex({self._writer_index}): {self!r}"
91
+ )
92
+
93
+ def _ensure_writable(self, length: int) -> None:
94
+ needed = self._writer_index + length
95
+ if needed > len(self._data):
96
+ self._data.extend(b"\x00" * (needed - len(self._data)))
97
+
98
+ # ------------------------------------------------------------------
99
+ # Raw bytes
100
+ # ------------------------------------------------------------------
101
+ def write_bytes(self, data: Union[bytes, bytearray]) -> "ByteBuf":
102
+ self._ensure_writable(len(data))
103
+ self._data[self._writer_index : self._writer_index + len(data)] = data
104
+ self._writer_index += len(data)
105
+ return self
106
+
107
+ def read_bytes(self, length: int) -> bytes:
108
+ self._check_readable(length)
109
+ result = bytes(self._data[self._reader_index : self._reader_index + length])
110
+ self._reader_index += length
111
+ return result
112
+
113
+ def read_all(self) -> bytes:
114
+ return self.read_bytes(self.readable_bytes())
115
+
116
+ def to_bytes(self) -> bytes:
117
+ """Full copy of the written content (ignores indices)."""
118
+ return bytes(self._data[: self._writer_index])
119
+
120
+ # ------------------------------------------------------------------
121
+ # Integers (big-endian, Netty's default)
122
+ # ------------------------------------------------------------------
123
+ def write_byte(self, value: int) -> "ByteBuf":
124
+ return self.write_bytes(struct.pack(">b" if value < 0 else ">B", value & 0xFF if value >= 0 else value))
125
+
126
+ def read_byte(self) -> int:
127
+ return struct.unpack(">b", self.read_bytes(1))[0]
128
+
129
+ def read_unsigned_byte(self) -> int:
130
+ return struct.unpack(">B", self.read_bytes(1))[0]
131
+
132
+ def write_short(self, value: int) -> "ByteBuf":
133
+ return self.write_bytes(struct.pack(">h", value))
134
+
135
+ def read_short(self) -> int:
136
+ return struct.unpack(">h", self.read_bytes(2))[0]
137
+
138
+ def write_unsigned_short(self, value: int) -> "ByteBuf":
139
+ return self.write_bytes(struct.pack(">H", value))
140
+
141
+ def read_unsigned_short(self) -> int:
142
+ return struct.unpack(">H", self.read_bytes(2))[0]
143
+
144
+ def write_int(self, value: int) -> "ByteBuf":
145
+ return self.write_bytes(struct.pack(">i", value))
146
+
147
+ def read_int(self) -> int:
148
+ return struct.unpack(">i", self.read_bytes(4))[0]
149
+
150
+ def write_unsigned_int(self, value: int) -> "ByteBuf":
151
+ return self.write_bytes(struct.pack(">I", value))
152
+
153
+ def read_unsigned_int(self) -> int:
154
+ return struct.unpack(">I", self.read_bytes(4))[0]
155
+
156
+ def write_long(self, value: int) -> "ByteBuf":
157
+ return self.write_bytes(struct.pack(">q", value))
158
+
159
+ def read_long(self) -> int:
160
+ return struct.unpack(">q", self.read_bytes(8))[0]
161
+
162
+ def write_float(self, value: float) -> "ByteBuf":
163
+ return self.write_bytes(struct.pack(">f", value))
164
+
165
+ def read_float(self) -> float:
166
+ return struct.unpack(">f", self.read_bytes(4))[0]
167
+
168
+ def write_double(self, value: float) -> "ByteBuf":
169
+ return self.write_bytes(struct.pack(">d", value))
170
+
171
+ def read_double(self) -> float:
172
+ return struct.unpack(">d", self.read_bytes(8))[0]
173
+
174
+ def write_boolean(self, value: bool) -> "ByteBuf":
175
+ return self.write_byte(1 if value else 0)
176
+
177
+ def read_boolean(self) -> bool:
178
+ return self.read_byte() != 0
179
+
180
+ # ------------------------------------------------------------------
181
+ # VarInt / VarLong (Minecraft / protobuf style) — handy for wire protocols
182
+ # ------------------------------------------------------------------
183
+ def write_varint(self, value: int) -> "ByteBuf":
184
+ value &= 0xFFFFFFFF
185
+ while True:
186
+ byte = value & 0x7F
187
+ value >>= 7
188
+ if value:
189
+ self.write_byte(byte | 0x80)
190
+ else:
191
+ self.write_byte(byte)
192
+ break
193
+ return self
194
+
195
+ def read_varint(self) -> int:
196
+ result = 0
197
+ shift = 0
198
+ while True:
199
+ byte = self.read_unsigned_byte()
200
+ result |= (byte & 0x7F) << shift
201
+ if not (byte & 0x80):
202
+ break
203
+ shift += 7
204
+ if shift >= 35:
205
+ raise CorruptedFrameException("VarInt is too big")
206
+ if result & 0x80000000:
207
+ result -= 0x100000000
208
+ return result
209
+
210
+ def write_varlong(self, value: int) -> "ByteBuf":
211
+ value &= 0xFFFFFFFFFFFFFFFF
212
+ while True:
213
+ byte = value & 0x7F
214
+ value >>= 7
215
+ if value:
216
+ self.write_byte(byte | 0x80)
217
+ else:
218
+ self.write_byte(byte)
219
+ break
220
+ return self
221
+
222
+ def read_varlong(self) -> int:
223
+ result = 0
224
+ shift = 0
225
+ while True:
226
+ byte = self.read_unsigned_byte()
227
+ result |= (byte & 0x7F) << shift
228
+ if not (byte & 0x80):
229
+ break
230
+ shift += 7
231
+ if shift >= 70:
232
+ raise CorruptedFrameException("VarLong is too big")
233
+ if result & 0x8000000000000000:
234
+ result -= 0x10000000000000000
235
+ return result
236
+
237
+ # ------------------------------------------------------------------
238
+ # Strings
239
+ # ------------------------------------------------------------------
240
+ def write_string(self, value: str, encoding: str = "utf-8") -> "ByteBuf":
241
+ encoded = value.encode(encoding)
242
+ self.write_varint(len(encoded))
243
+ return self.write_bytes(encoded)
244
+
245
+ def read_string(self, encoding: str = "utf-8") -> str:
246
+ length = self.read_varint()
247
+ return self.read_bytes(length).decode(encoding)
248
+
249
+ def __len__(self) -> int:
250
+ return self.readable_bytes()
251
+
252
+ def __repr__(self) -> str:
253
+ return (
254
+ f"ByteBuf(readerIndex={self._reader_index}, "
255
+ f"writerIndex={self._writer_index}, capacity={len(self._data)})"
256
+ )