/** * Encodes a stdClass instance. * * @param \stdClass $data The EMV TLV data structure. * * @return string Returns a string representing the encoded EMV TLV data * structure. */ protected function encodeObject(\stdClass $data) { if (!isset($data->tag, $data->value) || !in_array(strlen($data->tag), array(2, 4, 6))) { return ''; } $value = (string) $data->value; if (Tag::isConstructed(hexdec(substr($data->tag, 0, 2)))) { $value = $this->encode($data->value); } $length = strlen($value) / 2; /* * ISO/IEC 7816-4:2013, sect. 5.2.2.2: "ISO/IEC 7816 supports length * fields [...] up to five bytes." * * This means that the value field can be made of 4294967295 (FFFFFFFF) * bytes at most. * * If the length excesses this value, bail out. */ if ($length > 4294967295.0) { return ''; } $bytes = $length < 128 ? 1 : ($length < 256 ? 2 : ($length < 65536 ? 3 : ($length < 16777216 ? 4 : 5))); if ($bytes === 1) { $length = sprintf('%02X', $length); } else { $length = sprintf('%032b', $length); $length = str_split($length, 8); $length = array_map(function ($bits) { return sprintf('%02X', bindec($bits)); }, $length); $length = array_filter($length, function ($bits) { return '00' !== $bits; }); $length = implode('', $length); $length = strval(79 + $bytes) . $length; } return sprintf('%s%s%s', $data->tag, $length, $value); }
/** * Decodes a list of bytes representing some EMV TLV-encoded data. * * @param int[] $bytes The list of bytes. * * @return \stdClass[] Returns a list of \stdClass instances containing the * parsed EMV TLV-encoded data. May contain nested lists of objects. */ protected function decodeTlv($bytes) { $tree = array(); $extent = count($bytes); $cursor = 0; while ($cursor < $extent) { // Tag // ----------------------------------------------------------------- if (!isset($bytes[$cursor])) { break; } $tag = array($bytes[$cursor]); // Is the tag valid? No? Move the cursor forward and skip any // remaining computation. if (!Tag::isValid($tag[0])) { /** * Trigger an error? Throw an exception? Log something? No! To * be really useful such a message should include the original * EMV-TLV data block, which we cannot disclose inside the log * files because of PCI DSS requirements. */ $cursor++; continue; } $tagIsConstructed = Tag::isConstructed($tag[0]); if (Tag::isMultiByte($tag[0])) { // Two-byte tag // ------------------------------------------------------------- $cursor++; if (!isset($bytes[$cursor])) { break; } $tag[] = $bytes[$cursor]; if (!Tag::isLast($bytes[$cursor])) { // Three-byte tag // ------------------------------------------------------------- $cursor++; if (!isset($bytes[$cursor])) { break; } $tag[] = $bytes[$cursor]; } } $tag = array_map(function ($byte) { return sprintf('%02X', $byte); }, $tag); $tag = implode('', $tag); $cursor++; // Length // ----------------------------------------------------------------- if (!isset($bytes[$cursor])) { break; } $length = $bytes[$cursor]; if (!Length::isValid($length)) { /** * Trigger an error? Throw an exception? Log something? No! To * be really useful such a message should include the original * EMV TLV data block, which we cannot disclose inside the log * files because of PCI DSS requirements. */ break; } $length = Length::getLength($length); if (Length::isMultiByte($bytes[$cursor])) { $length_cursor = 0; $length_extent = $length; $length = array(); while ($length_cursor < $length_extent) { $cursor++; if (!isset($bytes[$cursor])) { break; } $length[] = $bytes[$cursor] << ($length_extent - $length_cursor - 1) * 8; $length_cursor++; } if (!isset($bytes[$cursor])) { break; } $length_output = 0; foreach ($length as $length_part) { $length_output = $length_output | $length_part; } $length = $length_output; } $cursor++; // Value // ----------------------------------------------------------------- if (!isset($bytes[$cursor])) { break; } $value = array_slice($bytes, $cursor, $length); if ($tagIsConstructed) { $value = $this->decodeTlv($value); } else { $value = array_map(function ($byte) { return sprintf('%02x', $byte); }, $value); $value = implode('', $value); } $cursor += $length; // All together now! // ----------------------------------------------------------------- $leaf = new \stdClass(); $leaf->name = Tag::getName($tag); $leaf->length = $length; $leaf->value = $value; // Add it to the output, now! // ----------------------------------------------------------------- $tree[$tag] = $leaf; } return $tree; }