@@ -229,18 +229,20 @@ class MySQL_Protocol {
229229 * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_ok_packet.html
230230 *
231231 * @param int $sequence_id The sequence ID of the packet.
232+ * @param int $server_status The status flags representing the server state.
232233 * @param int $affected_rows Number of rows affected by the query.
233234 * @param int $last_insert_id The last insert ID.
234- * @param int $server_status The status flags representing the server state.
235235 * @param int $warning_count The warning count.
236+ * @param int $packet_header The packet header, indicating an OK or EOF semantic.
236237 * @return string The OK packet.
237238 */
238239 public static function build_ok_packet (
239240 int $ sequence_id ,
240- int $ affected_rows ,
241- int $ last_insert_id ,
242241 int $ server_status ,
243- int $ warning_count
242+ int $ affected_rows = 0 ,
243+ int $ last_insert_id = 0 ,
244+ int $ warning_count = 0 ,
245+ int $ packet_header = self ::OK_PACKET_HEADER
244246 ): string {
245247 /**
246248 * Assemble the OK packet payload.
@@ -255,16 +257,47 @@ public static function build_ok_packet(
255257 */
256258 $ payload = pack (
257259 'Ca*a*vv ' ,
258- self :: OK_PACKET_HEADER , // (C) OK packet header.
260+ $ packet_header , // (C) OK packet header.
259261 self ::encode_length_encoded_int ( $ affected_rows ), // (a*) Affected rows.
260262 self ::encode_length_encoded_int ( $ last_insert_id ), // (a*) Last insert ID.
261263 $ server_status , // (v) Server status flags.
262264 $ warning_count , // (v) Server status flags.
263- // No human-readable message for simplicity
264265 );
265266 return self ::build_packet ( $ sequence_id , $ payload );
266267 }
267268
269+ /**
270+ * Build the OK packet with an EOF header.
271+ *
272+ * When the CLIENT_DEPRECATE_EOF capability is supported, an OK packet with
273+ * an EOF header is used to mark EOF, instead of the deprecated EOF packet.
274+ *
275+ * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_ok_packet.html
276+ *
277+ * @param int $sequence_id The sequence ID of the packet.
278+ * @param int $server_status The status flags representing the server state.
279+ * @param int $affected_rows Number of rows affected by the query.
280+ * @param int $last_insert_id The last insert ID.
281+ * @param int $warning_count The warning count.
282+ * @return string The OK packet.
283+ */
284+ public static function build_ok_packet_as_eof (
285+ int $ sequence_id ,
286+ int $ server_status ,
287+ int $ affected_rows = 0 ,
288+ int $ last_insert_id = 0 ,
289+ int $ warning_count = 0
290+ ): string {
291+ return self ::build_ok_packet (
292+ $ sequence_id ,
293+ $ server_status ,
294+ $ affected_rows ,
295+ $ last_insert_id ,
296+ $ warning_count ,
297+ self ::EOF_PACKET_HEADER
298+ );
299+ }
300+
268301 /**
269302 * Build the ERR packet.
270303 *
@@ -316,7 +349,7 @@ public static function build_err_packet(
316349 public static function build_eof_packet (
317350 int $ sequence_id ,
318351 int $ server_status ,
319- int $ warning_count
352+ int $ warning_count = 0
320353 ): string {
321354 /**
322355 * Assemble the EOF packet payload.
@@ -534,4 +567,68 @@ public static function encode_length_encoded_int( int $value ): string {
534567 public static function encode_length_encoded_string ( string $ value ): string {
535568 return self ::encode_length_encoded_int ( strlen ( $ value ) ) . $ value ;
536569 }
570+
571+ /**
572+ * Read MySQL length-encoded integer from a payload and advance the offset.
573+ *
574+ * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_integers.html
575+ *
576+ * @param string $payload A payload of bytes to read from.
577+ * @param int $offset And offset to start reading from within the payload.
578+ * The value will be advanced by the number of bytes read.
579+ * @return int The decoded integer value.
580+ */
581+ public static function read_length_encoded_int ( string $ payload , int &$ offset ): int {
582+ $ first_byte = ord ( $ payload [ $ offset ] ?? "\0" );
583+ $ offset += 1 ;
584+
585+ if ( $ first_byte < 0xfb ) {
586+ $ value = $ first_byte ;
587+ } elseif ( 0xfb === $ first_byte ) {
588+ $ value = 0 ;
589+ } elseif ( 0xfc === $ first_byte ) {
590+ $ value = unpack ( 'v ' , $ payload , $ offset )[1 ];
591+ $ offset += 2 ;
592+ } elseif ( 0xfd === $ first_byte ) {
593+ $ value = unpack ( 'VX ' , $ payload , $ offset )[1 ];
594+ $ offset += 3 ;
595+ } else {
596+ $ value = unpack ( 'P ' , $ payload , $ offset )[1 ];
597+ $ offset += 8 ;
598+ }
599+ return $ value ;
600+ }
601+
602+ /**
603+ * Read MySQL length-encoded string from a payload and advance the offset.
604+ *
605+ * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html
606+ *
607+ * @param string $payload A payload of bytes to read from.
608+ * @param int $offset And offset to start reading from within the payload.
609+ * The value will be advanced by the number of bytes read.
610+ * @return string The decoded string value.
611+ */
612+ public static function read_length_encoded_string ( string $ payload , int &$ offset ): string {
613+ $ length = self ::read_length_encoded_int ( $ payload , $ offset );
614+ $ value = substr ( $ payload , $ offset , $ length );
615+ $ offset += $ length ;
616+ return $ value ;
617+ }
618+
619+ /**
620+ * Read MySQL null-terminated string from a payload and advance the offset.
621+ *
622+ * @see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html
623+ *
624+ * @param string $payload A payload of bytes to read from.
625+ * @param int $offset And offset to start reading from within the payload.
626+ * The value will be advanced by the number of bytes read.
627+ * @return string The decoded string value.
628+ */
629+ public static function read_null_terminated_string ( string $ payload , int &$ offset ): string {
630+ $ value = unpack ( 'Z* ' , $ payload , $ offset )[1 ];
631+ $ offset += strlen ( $ value ) + 1 ;
632+ return $ value ;
633+ }
537634}
0 commit comments