@@ -460,4 +460,81 @@ public function testDecodedTextStringLengthMatchesContent(): void
460460 $ this ->assertSame (5 , $ decoded ['length ' ]);
461461 $ this ->assertSame (16 , $ decoded ['total_length ' ]); // 8 header + 8 padded
462462 }
463+
464+ // -----------------------------------------------------------------------
465+ // Security hardening tests
466+ // -----------------------------------------------------------------------
467+
468+ public function testRejectsDeclaredLengthExceedingBuffer (): void
469+ {
470+ // Header claiming 1000 bytes of value, but only 10 bytes provided
471+ $ header = pack ('C3C ' , 0x42 , 0x00 , 0x01 , 0x07 ); // tag=0x420001, type=TextString
472+ $ header .= pack ('N ' , 1000 ); // length = 1000
473+ $ body = str_repeat ("\x00" , 10 );
474+ $ buf = $ header . $ body ;
475+ $ this ->expectException (\RuntimeException::class);
476+ $ this ->expectExceptionMessageMatches ('/exceeds buffer/ ' );
477+ Ttlv::decode ($ buf );
478+ }
479+
480+ public function testAcceptsDeclaredLengthThatExactlyFitsBuffer (): void
481+ {
482+ $ encoded = Ttlv::encodeInteger (0x420001 , 42 );
483+ $ decoded = Ttlv::decode ($ encoded );
484+ $ this ->assertSame (42 , $ decoded ['value ' ]);
485+ }
486+
487+ public function testRejectsZeroLengthBuffer (): void
488+ {
489+ $ this ->expectException (\RuntimeException::class);
490+ $ this ->expectExceptionMessageMatches ('/too short/ ' );
491+ Ttlv::decode ('' );
492+ }
493+
494+ public function testRejectsStructuresNestedDeeperThan32Levels (): void
495+ {
496+ // Build 33 levels of nesting
497+ $ inner = Ttlv::encodeInteger (0x420001 , 42 );
498+ for ($ i = 0 ; $ i < 33 ; $ i ++) {
499+ $ inner = Ttlv::encodeStructure (0x420001 , [$ inner ]);
500+ }
501+ $ this ->expectException (\RuntimeException::class);
502+ $ this ->expectExceptionMessageMatches ('/depth/ ' );
503+ Ttlv::decode ($ inner );
504+ }
505+
506+ public function testAcceptsStructuresNestedExactly32LevelsDeep (): void
507+ {
508+ // Build 31 wrapping levels (root is depth 0, innermost is depth 31)
509+ $ inner = Ttlv::encodeInteger (0x420001 , 42 );
510+ for ($ i = 0 ; $ i < 31 ; $ i ++) {
511+ $ inner = Ttlv::encodeStructure (0x420001 , [$ inner ]);
512+ }
513+ $ decoded = Ttlv::decode ($ inner );
514+ $ this ->assertSame (Ttlv::TYPE_STRUCTURE , $ decoded ['type ' ]);
515+ }
516+
517+ public function testRejectsTruncatedHeader (): void
518+ {
519+ $ buf = pack ('C4 ' , 0x42 , 0x00 , 0x01 , 0x02 );
520+ $ this ->expectException (\RuntimeException::class);
521+ $ this ->expectExceptionMessageMatches ('/too short/ ' );
522+ Ttlv::decode ($ buf );
523+ }
524+
525+ public function testHandlesIntegerWithWrongLengthSafely (): void
526+ {
527+ // Header: tag=0x420001, type=Integer(0x02), length=3 (should be 4)
528+ $ buf = str_repeat ("\x00" , 16 );
529+ $ buf [0 ] = chr (0x42 ); $ buf [1 ] = chr (0x00 ); $ buf [2 ] = chr (0x01 );
530+ $ buf [3 ] = chr (0x02 ); // type = Integer
531+ $ buf = substr_replace ($ buf , pack ('N ' , 3 ), 4 , 4 ); // length = 3
532+ // Should either throw or handle safely — must not crash
533+ try {
534+ Ttlv::decode ($ buf );
535+ } catch (\Exception $ e ) {
536+ // Any exception is acceptable
537+ }
538+ $ this ->assertTrue (true , 'decoder did not crash on malformed integer length ' );
539+ }
463540}
0 commit comments