diff --git a/lib/FWIF/FWIF.php b/lib/FWIF/FWIF.php index 2b8d2a6..b9f449e 100644 --- a/lib/FWIF/FWIF.php +++ b/lib/FWIF/FWIF.php @@ -8,12 +8,14 @@ use DateTimeImmutable; use DateTimeZone; use InvalidArgumentException; +// TODO: IMPLEMENT TYPE_TIMESPAN + class FWIF { public const CONTENT_TYPE = 'text/plain; charset=us-ascii'; // TODO: come up with a mime type - public const DEFAULT = 0; + public const DEFAULT = 0; // Default behaviour public const DISCARD_MILLISECONDS = 0x01; // Always exclude the millisecond component from DateTime - public const EXCLUDE_VERSION = 0x02; // Exclude version byte at the start of the stream + public const EXCLUDE_VERSION = 0x02; // Exclude version byte at the start of the stream public const TYPE_NULL = 0; // NULL, no data public const TYPE_INTEGER = 0x01; // LEB128, implicit length @@ -23,20 +25,22 @@ class FWIF { public const TYPE_ARRAY = 0x05; // List of values, terminated with TRAILER public const TYPE_OBJECT = 0x06; // List of values with ASCII names, terminated with TRAILER public const TYPE_DATETIME = 0x07; // A gregorian year, month and day as well as an hour, minute, seconds and millisecond component, variable ranging from 4 to 7 bytes + public const TYPE_TIMESPAN = 0x08; // A period of time, follows the same format DATETIME but uses an additional flag to indicate negative periods public const TRAILER = 0xFF; // Termination byte public const VERSION = 0x01; // min 1, max 254 private const CODECS = [ - self::TYPE_NULL => 'Null', - self::TYPE_INTEGER => 'Integer', - self::TYPE_FLOAT => 'Float', - self::TYPE_STRING => 'String', - self::TYPE_ARRAY => 'Array', - self::TYPE_OBJECT => 'Object', - self::TYPE_BUFFER => 'Buffer', + self::TYPE_NULL => 'Null', + self::TYPE_INTEGER => 'Integer', + self::TYPE_FLOAT => 'Float', + self::TYPE_STRING => 'String', + self::TYPE_ARRAY => 'Array', + self::TYPE_OBJECT => 'Object', + self::TYPE_BUFFER => 'Buffer', self::TYPE_DATETIME => 'DateTime', + self::TYPE_TIMESPAN => 'TimeSpan', ]; private const UTF8 = '%^(?:' // https://www.w3.org/International/questions/qa-forms-utf-8.en @@ -75,6 +79,8 @@ class FWIF { if(is_object($data) || self::isAssocArray($data)) { if($data instanceof DateTimeInterface) return self::TYPE_DATETIME; + if($data instanceof DateInterval) + return self::TYPE_TIMESPAN; return self::TYPE_OBJECT; } if(is_array($data)) @@ -155,12 +161,72 @@ class FWIF { return $number; } - // I still don't like these + private const FLOAT_MZ = 0x04; + private const FLOAT_EZ = 0x08; + private const FLOAT_ZERO = self::FLOAT_MZ | self::FLOAT_EZ; + private const FLOAT_NAN = 0x20; + private const FLOAT_INF = 0x40; + private const FLOAT_SIGN = 0x80; + private const FLOAT_NINF = self::FLOAT_INF | self::FLOAT_SIGN; + + private const FLOAT_DIV = 0x7FFFFFFF; + private static function encodeFloat(float $number, int $flags): string { - return pack('E', $number); + if(is_nan($number)) + $packed = chr(self::FLOAT_NAN); + elseif($number === INF) + $packed = chr(self::FLOAT_INF); + elseif($number === -INF) + $packed = chr(self::FLOAT_NINF); + else { + $byte1 = 0; $packed = ''; + + if($number < 0.0 || ($number ** -1 === -INF)) + $byte1 |= self::FLOAT_SIGN; + + if($number === 0.0) + $byte1 |= self::FLOAT_ZERO; + else { + $numAbs = abs($number); + $exp = (int)(floor(log($numAbs, 2)) + 1); + $man = (int)(($numAbs * (2 ** -$exp)) * self::FLOAT_DIV); + if($exp != 0) + $packed .= self::encodeInteger($exp, $flags); + else + $byte1 |= self::FLOAT_EZ; + if($man > 0) + $packed .= pack('N', $man); + else + $byte1 |= self::FLOAT_MZ; + } + + $packed = chr($byte1) . $packed; + } + + return $packed; } private static function decodeFloat($data, int $flags): float { - return unpack('E', fread($data, 8))[1]; + $byte1 = ord(fgetc($data)); + + if($byte1 & self::FLOAT_NAN) + return NAN; + if($byte1 & self::FLOAT_INF) + return ($byte1 & self::FLOAT_SIGN) ? -INF : INF; + if(($byte1 & self::FLOAT_ZERO) === self::FLOAT_ZERO) + return ($byte1 & self::FLOAT_SIGN) ? -0.0 : 0.0; + + $exp = 0; $man = 0; + + if(!($byte1 & self::FLOAT_EZ)) + $exp = self::decodeInteger($data, $flags); + if(!($byte1 & self::FLOAT_MZ)) + $man = ((float)unpack('N', fread($data, 4))[1]) / self::FLOAT_DIV; + + $number = $man * (2 ** $exp); + if($byte1 & self::FLOAT_SIGN) + $number *= -1; + + return $number; } private static function encodeString(string $string, int $flags): string { @@ -238,8 +304,9 @@ class FWIF { return fread($data, self::decodeInteger($data, $flags)); } - private const DATETIME_FLAG_TIME = 0x40; - private const DATETIME_FLAG_MILLI = 0x4000; + private const DATETIME_FLAG_NEGA = 0x20; + private const DATETIME_FLAG_TIME = 0x40; + private const DATETIME_FLAG_MILLI = 0x4000; private const DATETIME_YEAR_SIGN = 0x40000000; private const DATETIME_YEAR_MASK = 0x3FFF; @@ -263,12 +330,12 @@ class FWIF { private const DATETIME_MILLI_HI_SHIFT = 8; // >> private const DATETIME_MILLI_LO_MASK = 0x0FF; - /* +--------+--------+ - * |.YYYYYYY|YYYYYYYY| - * |MMMMDDDD|DT.HHHHH| - * |.Wmmmmmm|SSSSSSww| - * |wwwwwwww| | - * +--------+--------+ + /* +--------+--------+ Y - Signed 15-bit year W - w enable flag + * |.YYYYYYY|YYYYYYYY| M - Unsigned 4-bit month m - unsigned 6-bit minutes + * |MMMMDDDD|DTNHHHHH| D - Unsigned 5-bit Day S - unsigned 6-bit seconds + * |.Wmmmmmm|SSSSSSww| T - WmSw enable flag w - unsigned 10-bit millisecs + * |wwwwwwww| | N - Negative flag (for TYPE_PERIOD) + * +--------+--------+ H - Unsigned 5-bit hours */ private static function encodeDateTime(DateTimeInterface $dt, int $flags): string { @@ -349,4 +416,11 @@ class FWIF { return new DateTimeImmutable($dt); } + + private static function encodeTimeSpan(DateInterval $di, int $flags): string { + return ''; + } + private static function decodeTimeSpan($data, int $flags): DateInterval { + return new \DateInterval('P1Y'); + } } diff --git a/public/index.php b/public/index.php index f79e493..b4e15f2 100644 --- a/public/index.php +++ b/public/index.php @@ -20,7 +20,6 @@ if($request->match('GET', '/packages')) { $jsonEncoded = json_encode($packages, JSON_INVALID_UTF8_SUBSTITUTE); echo 'JSON ' . strlen($jsonEncoded) . ' bytes ' . $jsonEncoded; - echo "\r\n\r\n--------------------\r\n\r\n"; $hexdump = bin2hex($encoded); $hexdumpSect = 8; $hexdumpSize = 32; diff --git a/src/Dummy/DummyPackage.php b/src/Dummy/DummyPackage.php index df8a647..3005935 100644 --- a/src/Dummy/DummyPackage.php +++ b/src/Dummy/DummyPackage.php @@ -36,10 +36,37 @@ class DummyPackage implements IPackage, \JsonSerializable { 'neg32' => -12345678, 'neg64' => -1234567890987654, 'float' => 12345.6789, + 'float2' => 8.0, + 'floatNeg' => -12345.6789, + 'floatPi' => M_PI, + 'floatE' => M_E, + 'floatLog2E' => M_LOG2E, + 'floatLog10E' => M_LOG10E, + 'floatLn2' => M_LN2, + 'floatLn10' => M_LN10, + 'floatPi2' => M_PI_2, + 'floatPi4' => M_PI_4, + 'floatM1Pi' => M_1_PI, + 'floatM2Pi' => M_2_PI, + 'floatSqrtPi' => M_SQRTPI, + 'float2SqrtPi' => M_2_SQRTPI, + 'floatSqrt2' => M_SQRT2, + 'floatSqrt3' => M_SQRT3, + 'floatSqrt12' => M_SQRT1_2, + 'floatLnPi' => M_LNPI, + 'floatEuler' => M_EULER, + //'floatNaN' => NAN, + //'floatInf' => INF, + //'floatNegInf' => -INF, + 'floatZero' => 0.0, + 'floatNegZero' => -0.0, 'invalid' => "\xFF\x25\x25\x02\xFF蠕。蝮F鄒守清\xFF\xFF\xFF", 'datetime' => new \DateTime('2013-01-27 23:14:44 CET'), 'datetimeNegative' => new \DateTime('-2013-01-27 23:14:44 CET'), 'datetimeNow' => new \DateTime(), + 'period' => (new \DateTime())->diff(new \DateTime('2013-01-27 23:14:44 CET')), + 'periodNegative' => (new \DateTime('2013-01-27 23:14:44 CET'))->diff(new \DateTime()), + 'periodZero' => (new \DateTime())->diff(new \DateTime()), 'array' => ['e', 'a', 0x55], 'object' => new \stdClass, 'misaka' => '御坂 美琴',