Build Reactive Signals for Bluesky's AT Protocol Firehose in Laravel
at dev 3.1 kB view raw
1<?php 2 3declare(strict_types=1); 4 5namespace SocialDept\AtpSignals\Binary; 6 7use RuntimeException; 8 9/** 10 * Binary data reader with position tracking. 11 * 12 * Provides stream-like interface for reading from binary strings. 13 */ 14class Reader 15{ 16 private int $position = 0; 17 18 public function __construct( 19 private readonly string $data, 20 ) { 21 } 22 23 /** 24 * Get current position in the data. 25 */ 26 public function getPosition(): int 27 { 28 return $this->position; 29 } 30 31 /** 32 * Get total length of data. 33 */ 34 public function getLength(): int 35 { 36 return strlen($this->data); 37 } 38 39 /** 40 * Check if there's more data to read. 41 */ 42 public function hasMore(): bool 43 { 44 return $this->position < strlen($this->data); 45 } 46 47 /** 48 * Get remaining bytes count. 49 */ 50 public function remaining(): int 51 { 52 return strlen($this->data) - $this->position; 53 } 54 55 /** 56 * Peek at the next byte without advancing position. 57 * 58 * @throws RuntimeException If no more data available 59 */ 60 public function peek(): int 61 { 62 if (! $this->hasMore()) { 63 throw new RuntimeException('Unexpected end of data'); 64 } 65 66 return ord($this->data[$this->position]); 67 } 68 69 /** 70 * Read a single byte and advance position. 71 * 72 * @throws RuntimeException If no more data available 73 */ 74 public function readByte(): int 75 { 76 $byte = $this->peek(); 77 $this->position++; 78 79 return $byte; 80 } 81 82 /** 83 * Read exactly N bytes and advance position. 84 * 85 * @throws RuntimeException If not enough data available 86 */ 87 public function readBytes(int $length): string 88 { 89 if ($this->remaining() < $length) { 90 throw new RuntimeException("Cannot read {$length} bytes, only {$this->remaining()} remaining"); 91 } 92 93 $bytes = substr($this->data, $this->position, $length); 94 $this->position += $length; 95 96 return $bytes; 97 } 98 99 /** 100 * Read a varint (variable-length integer). 101 * 102 * @throws RuntimeException If varint is malformed 103 */ 104 public function readVarint(): int 105 { 106 return Varint::decode($this->data, $this->position); 107 } 108 109 /** 110 * Get all remaining data without advancing position. 111 */ 112 public function peekRemaining(): string 113 { 114 return substr($this->data, $this->position); 115 } 116 117 /** 118 * Read all remaining data and advance position to end. 119 */ 120 public function readRemaining(): string 121 { 122 $remaining = $this->peekRemaining(); 123 $this->position = strlen($this->data); 124 125 return $remaining; 126 } 127 128 /** 129 * Skip N bytes forward. 130 * 131 * @throws RuntimeException If trying to skip past end 132 */ 133 public function skip(int $bytes): void 134 { 135 if ($this->remaining() < $bytes) { 136 throw new RuntimeException("Cannot skip {$bytes} bytes, only {$this->remaining()} remaining"); 137 } 138 139 $this->position += $bytes; 140 } 141}