Last active
August 29, 2015 14:11
-
-
Save er1c/0e792017d9d03be4bc22 to your computer and use it in GitHub Desktop.
Scala Library for Decoding/Deserializing [MC-NBFX]: .NET Binary Format: XML Data Structure and [MC-NBFS]: .NET Binary Format: SOAP Data Structure
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package fm.util | |
import java.io.{ByteArrayInputStream, ByteArrayOutputStream} | |
import org.scalatest.{FunSuite, Matchers} | |
class TestWCFBinaryXml extends FunSuite with Matchers with Logging { | |
private val testDictionary: Map[Long, String] = Map[Long, String]( | |
0l -> "str0", | |
4l -> "str4", | |
8l -> "str8", | |
10l -> "str10", | |
11l -> "str11", | |
14l -> "str14", | |
21l -> "str21", | |
26l -> "str26", | |
38l -> "str38", | |
56l -> "str56", | |
108l -> "str108", | |
110l -> "str110", | |
144l -> "str144", | |
154l -> "str154", | |
196l -> "str196", | |
236l -> "str236", | |
416l -> "str416", | |
880l -> "str880", | |
910l -> "str910", | |
912l -> "str912", | |
916l -> "str916" | |
) | |
private def check(bytes: Array[Byte], value: String): Unit = { | |
val bis = new ByteArrayInputStream(bytes) | |
val bos = new ByteArrayOutputStream() | |
val decoder = new WCFBinaryXml(bis, bos, testDictionary) | |
try { | |
decoder.decode() | |
} catch { | |
case e: Exception => | |
logger.warn(s"Current output stream contents: ${bos.toString("UTF-8")}") | |
throw e | |
} | |
bos.toString("UTF-8").trim() should equal(value) | |
} | |
// Port the "Structure Examples" For Testing Implementation | |
test("EndElement") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x01).map { _.toByte }, """<doc></doc>""") } | |
test("Comment") { check(Array(0x02, 0x07, 0x63, 0x6F, 0x6D, 0x6D, 0x65, 0x6E, 0x74).map { _.toByte }, """<!--comment-->""") } | |
test("Array") { check(Array(0x03, 0x40, 0x03, 0x61, 0x72, 0x72, 0x01, 0x8B, 0x03, 0x33, 0x33, 0x88, 0x88, 0xDD, 0xDD).map { _.toByte }, | |
"""<arr>13107</arr> | |
|<arr>-30584</arr> | |
|<arr>-8739</arr>""".stripMargin) } | |
test("ShortAttribute") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x04, 0x04, 0x61, 0x74, 0x74, 0x72, 0x84, 0x01).map { _.toByte }, """<doc attr="false"></doc>""") } | |
test("Attribute") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x09, 0x03, 0x70, 0x72, 0x65, 0x0A, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x61, 0x62, 0x63, 0x05, 0x03, 0x70, 0x72, 0x65, 0x04, 0x61, 0x74, 0x74, 0x72, 0x84, 0x01).map { _.toByte }, """<doc xmlns:pre="http://abc" pre:attr="false"></doc>""") } | |
test("ShortDictionaryAttribute") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x06, 0x08, 0x86, 0x01).map { _.toByte }, """<doc str8="true"></doc>""") } | |
test("DictionaryAttribute") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x09, 0x03, 0x70, 0x72, 0x65, 0x0A, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x61, 0x62, 0x63, 0x07, 0x03, 0x70, 0x72, 0x65, 0x00, 0x86, 0x01).map { _.toByte }, """<doc xmlns:pre="http://abc" pre:str0="true"></doc>""") } | |
test("ShortXmlnsAttribute") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x08, 0x0A, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x61, 0x62, 0x63, 0x01).map { _.toByte }, """<doc xmlns="http://abc"></doc>""") } | |
test("XmlnsAttribute") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x09, 0x01, 0x70, 0x0A, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x61, 0x62, 0x63, 0x01).map { _.toByte }, """<doc xmlns:p="http://abc"></doc>""") } | |
test("ShortDictionaryXmlnsAttribute") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x0A, 0x04, 0x01).map { _.toByte }, """<doc xmlns="str4"></doc>""") } | |
test("DictionaryXmlnsAttribute") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x0B, 0x01, 0x70, 0x04, 0x01).map { _.toByte }, """<doc xmlns:p="str4"></doc>""") } | |
test("PrefixDictionaryAttributeF") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x09, 0x01, 0x66, 0x0A, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x61, 0x62, 0x63, 0x11, 0x0B, 0x98, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x01).map { _.toByte }, """<doc xmlns:f="http://abc" f:str11="hello"></doc>""") } | |
test("PrefixDictionaryAttributeX") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x09, 0x01, 0x78, 0x0A, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x61, 0x62, 0x63, 0x23, 0x15, 0x98, 0x05, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x01).map { _.toByte }, """<doc xmlns:x="http://abc" x:str21="world"></doc>""") } | |
test("PrefixAttributeK") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x09, 0x01, 0x6B, 0x0A, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x61, 0x62, 0x63, 0x30, 0x04, 0x61, 0x74, 0x74, 0x72, 0x86, 0x01).map { _.toByte }, """<doc xmlns:k="http://abc" k:attr="true"></doc>""") } | |
test("PrefixAttributeZ") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x09, 0x01, 0x7A, 0x0A, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x61, 0x62, 0x63, 0x3F, 0x03, 0x61, 0x62, 0x63, 0x98, 0x03, 0x78, 0x79, 0x7A, 0x01).map { _.toByte }, """<doc xmlns:z="http://abc" z:abc="xyz"></doc>""") } | |
test("ShortElement") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x01).map { _.toByte }, """<doc></doc>""") } | |
test("Element") { check(Array(0x41, 0x03, 0x70, 0x72, 0x65, 0x03, 0x64, 0x6F, 0x63, 0x09, 0x03, 0x70, 0x72, 0x65, 0x0A, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x61, 0x62, 0x63, 0x01).map { _.toByte }, """<pre:doc xmlns:pre="http://abc"></pre:doc>""") } | |
test("ShortDictionaryElement") { check(Array(0x42, 0x0E, 0x01).map { _.toByte }, """<str14></str14>""") } | |
test("DictionaryElement") { check(Array(0x43, 0x03, 0x70, 0x72, 0x65, 0x0E, 0x09, 0x03, 0x70, 0x72, 0x65, 0x0A, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x61, 0x62, 0x63, 0x01).map { _.toByte }, """<pre:str14 xmlns:pre="http://abc"></pre:str14>""") } | |
test("PrefixDictionaryElementA") { check(Array(0x44, 0x0A, 0x09, 0x01, 0x61, 0x0A, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x61, 0x62, 0x63, 0x01).map { _.toByte }, """<a:str10 xmlns:a="http://abc"></a:str10>""") } | |
test("PrefixDictionaryElementS") { check(Array(0x56, 0x26, 0x09, 0x01, 0x73, 0x0A, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x61, 0x62, 0x63, 0x01).map { _.toByte }, """<s:str38 xmlns:s="http://abc"></s:str38>""") } | |
test("PrefixElementA") { check(Array(0x5E, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x09, 0x01, 0x61, 0x0A, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x61, 0x62, 0x63, 0x01).map { _.toByte }, """<a:hello xmlns:a="http://abc"></a:hello>""") } | |
test("PrefixElementS") { check(Array(0x70, 0x09, 0x4D, 0x79, 0x4D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x09, 0x01, 0x73, 0x0A, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x61, 0x62, 0x63, 0x01).map { _.toByte }, """<s:MyMessage xmlns:s="http://abc"></s:MyMessage>""") } | |
test("ZeroText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x06, 0xA0, 0x03, 0x80, 0x01).map { _.toByte }, """<doc str416="0"></doc>""") } | |
test("ZeroTextWithEndElement") { check(Array(0x40, 0x03, 0x61, 0x62, 0x63, 0x81).map { _.toByte }, """<abc>0</abc>""") } | |
test("OneText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x06, 0x00, 0x82, 0x01).map { _.toByte }, """<doc str0="1"></doc>""") } | |
test("OneTextWithEndElement") { check(Array(0x40, 0x03, 0x61, 0x62, 0x63, 0x83).map { _.toByte }, """<abc>1</abc>""") } | |
test("FalseText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x06, 0x00, 0x84, 0x01).map { _.toByte }, """<doc str0="false"></doc>""") } | |
test("FalseTextWithEndElement") { check(Array(0x40, 0x03, 0x61, 0x62, 0x63, 0x85).map { _.toByte }, """<abc>false</abc>""") } | |
test("TrueText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x06, 0x00, 0x86, 0x01).map { _.toByte }, """<doc str0="true"></doc>""") } | |
test("TrueTextWithEndElement") { check(Array(0x40, 0x03, 0x61, 0x62, 0x63, 0x87).map { _.toByte }, """<abc>true</abc>""") } | |
test("Int8Text") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x06, 0xEC, 0x01, 0x88, 0xDE, 0x01).map { _.toByte }, """<doc str236="-34"></doc>""") } | |
test("Int8TextWithEndElement") { check(Array(0x42, 0x9A, 0x01, 0x89, 0x7F).map { _.toByte }, """<str154>127</str154>""") } | |
test("Int16Text") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x06, 0xEC, 0x01, 0x8A, 0x00, 0x80, 0x01).map { _.toByte }, """<doc str236="-32768"></doc>""") } | |
test("Int16TextWithEndElement") { check(Array(0x42, 0x9A, 0x01, 0x8B, 0xFF, 0x7F).map { _.toByte }, """<str154>32767</str154>""") } | |
test("Int32Text") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x06, 0xEC, 0x01, 0x8C, 0x15, 0xCD, 0x5B, 0x07, 0x01).map { _.toByte }, """<doc str236="123456789"></doc>""") } | |
test("Int32TextWithEndElement") { check(Array(0x42, 0x9A, 0x01, 0x8D, 0xFF, 0xFF, 0xFF, 0x7F).map { _.toByte }, """<str154>2147483647</str154>""") } | |
test("Int64Text") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x06, 0xEC, 0x01, 0x8E, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01).map { _.toByte }, """<doc str236="2147483648"></doc>""") } | |
test("Int64TextWithEndElement") { check(Array(0x42, 0x9A, 0x01, 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00).map { _.toByte }, """<str154>1099511627776</str154>""") } | |
test("FloatText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x04, 0x01, 0x61, 0x90, 0xCD, 0xCC, 0x8C, 0x3F, 0x01).map { _.toByte }, """<doc a="1.1"></doc>""") } | |
test("FloatTextWithEndElement") { check(Array(0x40, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x91, 0xCD, 0xCC, 0x01, 0x42).map { _.toByte }, """<Price>32.45</Price>""") } | |
test("DoubleText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x04, 0x01, 0x61, 0x92, 0x74, 0x57, 0x14, 0x8B, 0x0A, 0xBF, 0x05, 0x40, 0x01).map { _.toByte }, """<doc a="2.71828182845905"></doc>""") } | |
test("DoubleTextWithEndElement") { check(Array(0x40, 0x02, 0x50, 0x49, 0x93, 0x11, 0x2D, 0x44, 0x54, 0xFB, 0x21, 0x09, 0x40).map { _.toByte }, """<PI>3.14159265358979</PI>""") } | |
test("DecimalText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x04, 0x03, 0x69, 0x6E, 0x74, 0x94, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x2D, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01).map { _.toByte }, """<doc int="5.123456"></doc>""") } | |
test("DecimalTextWithEndElement") { check(Array(0x40, 0x08, 0x4D, 0x61, 0x78, 0x56, 0x61, 0x6C, 0x75, 0x65, 0x95, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF).map { _.toByte }, """<MaxValue>79228162514264337593543950335</MaxValue>""") } | |
test("DateTimeText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x06, 0x6E, 0x96, 0xFF, 0x3F, 0x37, 0xF4, 0x75, 0x28, 0xCA, 0x2B, 0x01).map { _.toByte }, """<doc str110="9999-12-31T23:59:59.9999999"></doc>""") } | |
test("DateTimeTextWithEndElement") { check(Array(0x42, 0x6C, 0x97, 0x00, 0x40, 0x8E, 0xF9, 0x5B, 0x47, 0xC8, 0x08).map { _.toByte }, """<str108>2006-05-17T00:00:00</str108>""") } | |
test("Chars8Text") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x98, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x01).map { _.toByte }, """<doc>hello</doc>""") } | |
test("Chars8TextWithEndElement") { check(Array(0x40, 0x01, 0x61, 0x99, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F).map { _.toByte }, """<a>hello</a>""") } | |
test("Chars16Text") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x9A, 0x05, 0x00, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x01).map { _.toByte }, """<doc>hello</doc>""") } | |
test("Chars16TextWithEndElement") { check(Array(0x40, 0x01, 0x61, 0x9B, 0x05, 0x00, 0x68, 0x65, 0x6C, 0x6C, 0x6F).map { _.toByte }, """<a>hello</a>""") } | |
test("Chars32Text") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x9C, 0x05, 0x00, 0x00, 0x00, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x01).map { _.toByte }, """<doc>hello</doc>""") } | |
test("Chars32TextWithEndElement") { check(Array(0x40, 0x01, 0x61, 0x9D, 0x05, 0x00, 0x00, 0x00, 0x68, 0x65, 0x6C, 0x6C, 0x6F).map { _.toByte }, """<a>hello</a>""") } | |
test("Bytes8Text") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x9E, 0x08, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01).map { _.toByte }, """<doc>AAECAwQFBgc=</doc>""") } | |
test("Bytes8TextWithEndElement") { check(Array(0x40, 0x06, 0x42, 0x61, 0x73, 0x65, 0x36, 0x34, 0x9F, 0x08, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07).map { _.toByte }, """<Base64>AAECAwQFBgc=</Base64>""") } | |
test("Bytes16Text") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0xA0, 0x08, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01).map { _.toByte }, """<doc>AAECAwQFBgc=</doc>""") } | |
test("Bytes16TextWithEndElement") { check(Array(0x40, 0x06, 0x42, 0x61, 0x73, 0x65, 0x36, 0x34, 0xA1, 0x08, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07).map { _.toByte }, """<Base64>AAECAwQFBgc=</Base64>""") } | |
test("Bytes32Text") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0xA2, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01).map { _.toByte }, """<doc>AAECAwQFBgc=</doc>""") } | |
test("Bytes32TextWithEndElement") { check(Array(0x40, 0x06, 0x42, 0x61, 0x73, 0x65, 0x36, 0x34, 0xA3, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07).map { _.toByte }, """<Base64>AAECAwQFBgc=</Base64>""") } | |
test("StartListText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x04, 0x01, 0x61, 0xA4, 0x88, 0x7B, 0x98, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x86, 0xA6, 0x01).map { _.toByte }, """<doc a="123 hello true"></doc>""") } | |
test("EndListText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x04, 0x01, 0x61, 0xA4, 0x88, 0x7B, 0x98, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x86, 0xA6, 0x01).map { _.toByte }, """<doc a="123 hello true"></doc>""") } | |
test("EmptyText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x04, 0x01, 0x61, 0xA8, 0x01).map { _.toByte }, """<doc a=""></doc>""") } | |
test("EmptyTextWithEndElement") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0xA9).map { _.toByte }, """<doc></doc>""") } | |
test("DictionaryText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x04, 0x02, 0x6E, 0x73, 0xAA, 0x38, 0x01).map { _.toByte }, """<doc ns="str56"></doc>""") } | |
test("DictionaryTextWithEndElement") { check(Array(0x40, 0x04, 0x54, 0x79, 0x70, 0x65, 0xAB, 0xC4, 0x01).map { _.toByte }, """<Type>str196</Type>""") } | |
test("UniqueIdText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0xAC, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x01).map { _.toByte }, """<doc>urn:uuid:33221100-5544-7766-8899-aabbccddeeff</doc>""") } | |
test("UniqueIdTextWithEndElement") { check(Array(0x42, 0x1A, 0xAD, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF).map { _.toByte }, """<str26>urn:uuid:33221100-5544-7766-8899-aabbccddeeff</str26>""") } | |
test("TimeSpanText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0xAE, 0x00, 0xC4, 0xF5, 0x32, 0xFF, 0xFF, 0xFF, 0xFF, 0x01).map { _.toByte }, """<doc>-PT5M44S</doc>""") } | |
test("TimeSpanTextWithEndElement") { check(Array(0x42, 0x94, 0x07, 0xAF, 0x00, 0xB0, 0x8E, 0xF0, 0x1B, 0x00, 0x00, 0x00).map { _.toByte }, """<str916>PT3H20M</str916>""") } | |
test("UuidText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0xB0, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x01).map { _.toByte }, """<doc>03020100-0504-0706-0809-0a0b0c0d0e0f</doc>""") } | |
test("UuidTextWithEndElement") { check(Array(0x40, 0x02, 0x49, 0x44, 0xB1, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F).map { _.toByte }, """<ID>03020100-0504-0706-0809-0a0b0c0d0e0f</ID>""") } | |
test("UInt64Text") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0xB2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01).map { _.toByte }, """<doc>18446744073709551615</doc>""") } | |
test("UInt64TextWithEndElement") { check(Array(0x42, 0x9A, 0x01, 0xB3, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF).map { _.toByte }, """<str154>18446744073709551614</str154>""") } | |
test("BoolText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0xB4, 0x01, 0x01).map { _.toByte }, """<doc>true</doc>""") } | |
test("BoolTextWithEndElement") { check(Array(0x03, 0x40, 0x03, 0x61, 0x72, 0x72, 0x01, 0xB5, 0x05, 0x01, 0x00, 0x01, 0x00, 0x01).map { _.toByte }, | |
"""<arr>true</arr> | |
|<arr>false</arr> | |
|<arr>true</arr> | |
|<arr>false</arr> | |
|<arr>true</arr>""".stripMargin) } | |
test("UnicodeChars8Text") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x04, 0x01, 0x75, 0xB6, 0x06, 0x75, 0x00, 0x6E, 0x00, 0x69, 0x00, 0x01).map { _.toByte }, """<doc u="甀渀椀"></doc>""") } | |
test("UnicodeChars8TextWithEndElement") { check(Array(0x40, 0x01, 0x55, 0xB7, 0x06, 0x75, 0x00, 0x6E, 0x00, 0x69, 0x00).map { _.toByte }, """<U>甀渀椀</U>""") } | |
test("UnicodeChars16Text") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x04, 0x03, 0x75, 0x31, 0x36, 0xB8, 0x08, 0x00, 0x75, 0x00, 0x6E, 0x00, 0x69, 0x00, 0x32, 0x00, 0x01).map { _.toByte }, """<doc u16="甀渀椀㈀"></doc>""") } | |
test("UnicodeChars16TextWithEndElement") { check(Array(0x40, 0x03, 0x55, 0x31, 0x36, 0xB9, 0x08, 0x00, 0x75, 0x00, 0x6E, 0x00, 0x69, 0x00, 0x32, 0x00).map { _.toByte }, """<U16>甀渀椀㈀</U16>""") } | |
test("UnicodeChars32Text") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x04, 0x03, 0x75, 0x33, 0x32, 0xBA, 0x04, 0x00, 0x00, 0x00, 0x33, 0x00, 0x32, 0x00, 0x01).map { _.toByte }, """<doc u32="㌀㈀"></doc>""") } | |
test("UnicodeChars32TextWithEndElement") { check(Array(0x40, 0x03, 0x55, 0x33, 0x32, 0xBB, 0x04, 0x00, 0x00, 0x00, 0x33, 0x00, 0x32, 0x00).map { _.toByte }, """<U32>㌀㈀</U32>""") } | |
test("QNameDictionaryText") { check(Array(0x40, 0x03, 0x64, 0x6F, 0x63, 0x06, 0xF0, 0x06, 0xBC, 0x08, 0x8E, 0x07, 0x01).map { _.toByte }, """<doc str880="i:str910"></doc>""") } | |
test("QNameDictionaryTextWithEndElement") { check(Array(0x40, 0x04, 0x54, 0x79, 0x70, 0x65, 0xBD, 0x12, 0x90, 0x07).map { _.toByte }, """<Type>s:str912</Type>""") } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package fm.util | |
import java.io.{BufferedInputStream, File, FileInputStream, FileOutputStream, InputStream, OutputStream, PrintStream} | |
import java.nio.{ByteBuffer, ByteOrder} | |
import spire.implicits._ | |
import spire.math._ | |
import fm.common.Base64 | |
import fm.common.Implicits._ | |
import java.text.SimpleDateFormat | |
import java.util.{Date, TimeZone} | |
import scala.annotation.tailrec | |
// Windows Communication Foundation (WCF) Binary XML Format | |
// | |
// “Officially” called [MC-NBFX]: .NET Binary Format: XML Data Structure | |
// Html Spec: http://msdn.microsoft.com/en-us/library/cc219210(v=PROT.10).aspx & | |
// PDF Spec: http://download.microsoft.com/download/9/5/E/95EF66AF-9026-4BB0-A41D-A4F81802D92C/[MC-NBFX].pdf | |
// Related: [MC-NBFS]: .NET Binary Format: SOAP Data Structure | |
// Html Spec: http://msdn.microsoft.com/en-us/library/cc219175.aspx | |
// PDF Spec: http://download.microsoft.com/download/9/5/E/95EF66AF-9026-4BB0-A41D-A4F81802D92C/[MC-NBFS].pdf | |
// The Dictionary Strings in this implementation are referenced from: http://msdn.microsoft.com/en-us/library/cc219187.aspx | |
object WCFBinaryXml { | |
// Input encrypted Binary Xml file, output decrypted Xml text | |
def decodeBinaryXml(in: File, out: File): Unit = { | |
decodeBinaryXml(new FileInputStream(in), new FileOutputStream(out)) | |
} | |
// Read an encrypted Binary Xml file and print decrypted Xml to stdout | |
def decodeBinaryXml(file: File): Unit = { | |
import scala.sys.process.stdout | |
decodeBinaryXml(new FileInputStream(file), stdout) | |
} | |
// Stream Encrypted Xml InputStream, and write decrypted Xml to an OutputStream | |
def decodeBinaryXml(is: InputStream, os: OutputStream): Unit = { | |
val decoder = new WCFBinaryXml(is, os) | |
decoder.decode() | |
} | |
private val dictionaryMap: Map[Long, String] = Map( | |
0x00l -> "mustUnderstand", | |
0x02l -> "Envelope", | |
0x04l -> "http://www.w3.org/2003/05/soap-envelope", | |
0x06l -> "http://www.w3.org/2005/08/addressing", | |
0x08l -> "Header", | |
0x0Al -> "Action", | |
0x0Cl -> "To", | |
0x0El -> "Body", | |
0x10l -> "Algorithm", | |
0x12l -> "RelatesTo", | |
0x14l -> "http://www.w3.org/2005/08/addressing/anonymous", | |
0x16l -> "URI", | |
0x18l -> "Reference", | |
0x1Al -> "MessageID", | |
0x1Cl -> "Id", | |
0x1El -> "Identifier", | |
0x20l -> "http://schemas.xmlsoap.org/ws/2005/02/rm", | |
0x22l -> "Transforms", | |
0x24l -> "Transform", | |
0x26l -> "DigestMethod", | |
0x28l -> "DigestValue", | |
0x2Al -> "Address", | |
0x2Cl -> "ReplyTo", | |
0x2El -> "SequenceAcknowledgement", | |
0x30l -> "AcknowledgementRange", | |
0x32l -> "Upper", | |
0x34l -> "Lower", | |
0x36l -> "BufferRemaining", | |
0x38l -> "http://schemas.microsoft.com/ws/2006/05/rm", | |
0x3Al -> "http://schemas.xmlsoap.org/ws/2005/02/rm/SequenceAcknowledgement", | |
0x3Cl -> "SecurityTokenReference", | |
0x3El -> "Sequence", | |
0x40l -> "MessageNumber", | |
0x42l -> "http://www.w3.org/2000/09/xmldsig#", | |
0x44l -> "http://www.w3.org/2000/09/xmldsig#enveloped-signature", | |
0x46l -> "KeyInfo", | |
0x48l -> "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", | |
0x4Al -> "http://www.w3.org/2001/04/xmlenc#", | |
0x4Cl -> "http://schemas.xmlsoap.org/ws/2005/02/sc", | |
0x4El -> "DerivedKeyToken", | |
0x50l -> "Nonce", | |
0x52l -> "Signature", | |
0x54l -> "SignedInfo", | |
0x56l -> "CanonicalizationMethod", | |
0x58l -> "SignatureMethod", | |
0x5Al -> "SignatureValue", | |
0x5Cl -> "DataReference", | |
0x5El -> "EncryptedData", | |
0x60l -> "EncryptionMethod", | |
0x62l -> "CipherData", | |
0x64l -> "CipherValue", | |
0x66l -> "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", | |
0x68l -> "Security", | |
0x6Al -> "Timestamp", | |
0x6Cl -> "Created", | |
0x6El -> "Expires", | |
0x70l -> "Length", | |
0x72l -> "ReferenceList", | |
0x74l -> "ValueType", | |
0x76l -> "Type", | |
0x78l -> "EncryptedHeader", | |
0x7Al -> "http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd", | |
0x7Cl -> "RequestSecurityTokenResponseCollection", | |
0x7El -> "http://schemas.xmlsoap.org/ws/2005/02/trust", | |
0x80l -> "http://schemas.xmlsoap.org/ws/2005/02/trust#BinarySecret", | |
0x82l -> "http://schemas.microsoft.com/ws/2006/02/transactions", | |
0x84l -> "s", | |
0x86l -> "Fault", | |
0x88l -> "MustUnderstand", | |
0x8Al -> "role", | |
0x8Cl -> "relay", | |
0x8El -> "Code", | |
0x90l -> "Reason", | |
0x92l -> "Text", | |
0x94l -> "Node", | |
0x96l -> "Role", | |
0x98l -> "Detail", | |
0x9Al -> "Value", | |
0x9Cl -> "Subcode", | |
0x9El -> "NotUnderstood", | |
0xA0l -> "qname", | |
0xA2l -> "", | |
0xA4l -> "From", | |
0xA6l -> "FaultTo", | |
0xA8l -> "EndpointReference", | |
0xAAl -> "PortType", | |
0xACl -> "ServiceName", | |
0xAEl -> "PortName", | |
0xB0l -> "ReferenceProperties", | |
0xB2l -> "RelationshipType", | |
0xB4l -> "Reply", | |
0xB6l -> "a", | |
0xB8l -> "http://schemas.xmlsoap.org/ws/2006/02/addressingidentity", | |
0xBAl -> "Identity", | |
0xBCl -> "Spn", | |
0xBEl -> "Upn", | |
0xC0l -> "Rsa", | |
0xC2l -> "Dns", | |
0xC4l -> "X509v3Certificate", | |
0xC6l -> "http://www.w3.org/2005/08/addressing/fault", | |
0xC8l -> "ReferenceParameters", | |
0xCAl -> "IsReferenceParameter", | |
0xCCl -> "http://www.w3.org/2005/08/addressing/reply", | |
0xCEl -> "http://www.w3.org/2005/08/addressing/none", | |
0xD0l -> "Metadata", | |
0xD2l -> "http://schemas.xmlsoap.org/ws/2004/08/addressing", | |
0xD4l -> "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous", | |
0xD6l -> "http://schemas.xmlsoap.org/ws/2004/08/addressing/fault", | |
0xD8l -> "http://schemas.xmlsoap.org/ws/2004/06/addressingex", | |
0xDAl -> "RedirectTo", | |
0xDCl -> "Via", | |
0xDEl -> "http://www.w3.org/2001/10/xml-exc-c14n#", | |
0xE0l -> "PrefixList", | |
0xE2l -> "InclusiveNamespaces", | |
0xE4l -> "ec", | |
0xE6l -> "SecurityContextToken", | |
0xE8l -> "Generation", | |
0xEAl -> "Label", | |
0xECl -> "Offset", | |
0xEEl -> "Properties", | |
0xF0l -> "Cookie", | |
0xF2l -> "wsc", | |
0xF4l -> "http://schemas.xmlsoap.org/ws/2004/04/sc", | |
0xF6l -> "http://schemas.xmlsoap.org/ws/2004/04/security/sc/dk", | |
0xF8l -> "http://schemas.xmlsoap.org/ws/2004/04/security/sc/sct", | |
0xFAl -> "http://schemas.xmlsoap.org/ws/2004/04/security/trust/RST/SCT", | |
0xFCl -> "http://schemas.xmlsoap.org/ws/2004/04/security/trust/RSTR/SCT", | |
0xFEl -> "RenewNeeded", | |
0x100l -> "BadContextToken", | |
0x102l -> "c", | |
0x104l -> "http://schemas.xmlsoap.org/ws/2005/02/sc/dk", | |
0x106l -> "http://schemas.xmlsoap.org/ws/2005/02/sc/sct", | |
0x108l -> "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT", | |
0x10Al -> "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/SCT", | |
0x10Cl -> "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT/Renew", | |
0x10El -> "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/SCT/Renew", | |
0x110l -> "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT/Cancel", | |
0x112l -> "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/SCT/Cancel", | |
0x114l -> "http://www.w3.org/2001/04/xmlenc#aes128-cbc", | |
0x116l -> "http://www.w3.org/2001/04/xmlenc#kw-aes128", | |
0x118l -> "http://www.w3.org/2001/04/xmlenc#aes192-cbc", | |
0x11Al -> "http://www.w3.org/2001/04/xmlenc#kw-aes192", | |
0x11Cl -> "http://www.w3.org/2001/04/xmlenc#aes256-cbc", | |
0x11El -> "http://www.w3.org/2001/04/xmlenc#kw-aes256", | |
0x120l -> "http://www.w3.org/2001/04/xmlenc#des-cbc", | |
0x122l -> "http://www.w3.org/2000/09/xmldsig#dsa-sha1", | |
0x124l -> "http://www.w3.org/2001/10/xml-exc-c14n#WithComments", | |
0x126l -> "http://www.w3.org/2000/09/xmldsig#hmac-sha1", | |
0x128l -> "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256", | |
0x12Al -> "http://schemas.xmlsoap.org/ws/2005/02/sc/dk/p_sha1", | |
0x12Cl -> "http://www.w3.org/2001/04/xmlenc#ripemd160", | |
0x12El -> "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p", | |
0x130l -> "http://www.w3.org/2000/09/xmldsig#rsa-sha1", | |
0x132l -> "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", | |
0x134l -> "http://www.w3.org/2001/04/xmlenc#rsa-1_5", | |
0x136l -> "http://www.w3.org/2000/09/xmldsig#sha1", | |
0x138l -> "http://www.w3.org/2001/04/xmlenc#sha256", | |
0x13Al -> "http://www.w3.org/2001/04/xmlenc#sha512", | |
0x13Cl -> "http://www.w3.org/2001/04/xmlenc#tripledes-cbc", | |
0x13El -> "http://www.w3.org/2001/04/xmlenc#kw-tripledes", | |
0x140l -> "http://schemas.xmlsoap.org/2005/02/trust/tlsnego#TLS_Wrap", | |
0x142l -> "http://schemas.xmlsoap.org/2005/02/trust/spnego#GSS_Wrap", | |
0x144l -> "http://schemas.microsoft.com/ws/2006/05/security", | |
0x146l -> "dnse", | |
0x148l -> "o", | |
0x14Al -> "Password", | |
0x14Cl -> "PasswordText", | |
0x14El -> "Username", | |
0x150l -> "UsernameToken", | |
0x152l -> "BinarySecurityToken", | |
0x154l -> "EncodingType", | |
0x156l -> "KeyIdentifier", | |
0x158l -> "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary", | |
0x15Al -> "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#HexBinary", | |
0x15Cl -> "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Text", | |
0x15El -> "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier", | |
0x160l -> "http://docs.oasis-open.org/wss/oasis-wss-kerberos-token-profile-1.1#GSS_Kerberosv5_AP_REQ", | |
0x162l -> "http://docs.oasis-open.org/wss/oasis-wss-kerberos-token-profile-1.1#GSS_Kerberosv5_AP_REQ1510", | |
0x164l -> "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID", | |
0x166l -> "Assertion", | |
0x168l -> "urn:oasis:names:tc:SAML:1.0:assertion", | |
0x16Al -> "http://docs.oasis-open.org/wss/oasis-wss-rel-token-profile-1.0.pdf#license", | |
0x16Cl -> "FailedAuthentication", | |
0x16El -> "InvalidSecurityToken", | |
0x170l -> "InvalidSecurity", | |
0x172l -> "k", | |
0x174l -> "SignatureConfirmation", | |
0x176l -> "TokenType", | |
0x178l -> "http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#ThumbprintSHA1", | |
0x17Al -> "http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKey", | |
0x17Cl -> "http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKeySHA1", | |
0x17El -> "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1", | |
0x180l -> "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0", | |
0x182l -> "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID", | |
0x184l -> "AUTH-HASH", | |
0x186l -> "RequestSecurityTokenResponse", | |
0x188l -> "KeySize", | |
0x18Al -> "RequestedTokenReference", | |
0x18Cl -> "AppliesTo", | |
0x18El -> "Authenticator", | |
0x190l -> "CombinedHash", | |
0x192l -> "BinaryExchange", | |
0x194l -> "Lifetime", | |
0x196l -> "RequestedSecurityToken", | |
0x198l -> "Entropy", | |
0x19Al -> "RequestedProofToken", | |
0x19Cl -> "ComputedKey", | |
0x19El -> "RequestSecurityToken", | |
0x1A0l -> "RequestType", | |
0x1A2l -> "Context", | |
0x1A4l -> "BinarySecret", | |
0x1A6l -> "http://schemas.xmlsoap.org/ws/2005/02/trust/spnego", | |
0x1A8l -> "http://schemas.xmlsoap.org/ws/2005/02/trust/tlsnego", | |
0x1AAl -> "wst", | |
0x1ACl -> "http://schemas.xmlsoap.org/ws/2004/04/trust", | |
0x1AEl -> "http://schemas.xmlsoap.org/ws/2004/04/security/trust/RST/Issue", | |
0x1B0l -> "http://schemas.xmlsoap.org/ws/2004/04/security/trust/RSTR/Issue", | |
0x1B2l -> "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue", | |
0x1B4l -> "http://schemas.xmlsoap.org/ws/2004/04/security/trust/CK/PSHA1", | |
0x1B6l -> "http://schemas.xmlsoap.org/ws/2004/04/security/trust/SymmetricKey", | |
0x1B8l -> "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Nonce", | |
0x1BAl -> "KeyType", | |
0x1BCl -> "http://schemas.xmlsoap.org/ws/2004/04/trust/SymmetricKey", | |
0x1BEl -> "http://schemas.xmlsoap.org/ws/2004/04/trust/PublicKey", | |
0x1C0l -> "Claims", | |
0x1C2l -> "InvalidRequest", | |
0x1C4l -> "RequestFailed", | |
0x1C6l -> "SignWith", | |
0x1C8l -> "EncryptWith", | |
0x1CAl -> "EncryptionAlgorithm", | |
0x1CCl -> "CanonicalizationAlgorithm", | |
0x1CEl -> "ComputedKeyAlgorithm", | |
0x1D0l -> "UseKey", | |
0x1D2l -> "http://schemas.microsoft.com/net/2004/07/secext/WS-SPNego", | |
0x1D4l -> "http://schemas.microsoft.com/net/2004/07/secext/TLSNego", | |
0x1D6l -> "t", | |
0x1D8l -> "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue", | |
0x1DAl -> "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue", | |
0x1DCl -> "http://schemas.xmlsoap.org/ws/2005/02/trust/Issue", | |
0x1DEl -> "http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey", | |
0x1E0l -> "http://schemas.xmlsoap.org/ws/2005/02/trust/CK/PSHA1", | |
0x1E2l -> "http://schemas.xmlsoap.org/ws/2005/02/trust/Nonce", | |
0x1E4l -> "RenewTarget", | |
0x1E6l -> "CancelTarget", | |
0x1E8l -> "RequestedTokenCancelled", | |
0x1EAl -> "RequestedAttachedReference", | |
0x1ECl -> "RequestedUnattachedReference", | |
0x1EEl -> "IssuedTokens", | |
0x1F0l -> "http://schemas.xmlsoap.org/ws/2005/02/trust/Renew", | |
0x1F2l -> "http://schemas.xmlsoap.org/ws/2005/02/trust/Cancel", | |
0x1F4l -> "http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey", | |
0x1F6l -> "Access", | |
0x1F8l -> "AccessDecision", | |
0x1FAl -> "Advice", | |
0x1FCl -> "AssertionID", | |
0x1FEl -> "AssertionIDReference", | |
0x200l -> "Attribute", | |
0x202l -> "AttributeName", | |
0x204l -> "AttributeNamespace", | |
0x206l -> "AttributeStatement", | |
0x208l -> "AttributeValue", | |
0x20Al -> "Audience", | |
0x20Cl -> "AudienceRestrictionCondition", | |
0x20El -> "AuthenticationInstant", | |
0x210l -> "AuthenticationMethod", | |
0x212l -> "AuthenticationStatement", | |
0x214l -> "AuthorityBinding", | |
0x216l -> "AuthorityKind", | |
0x218l -> "AuthorizationDecisionStatement", | |
0x21Al -> "Binding", | |
0x21Cl -> "Condition", | |
0x21El -> "Conditions", | |
0x220l -> "Decision", | |
0x222l -> "DoNotCacheCondition", | |
0x224l -> "Evidence", | |
0x226l -> "IssueInstant", | |
0x228l -> "Issuer", | |
0x22Al -> "Location", | |
0x22Cl -> "MajorVersion", | |
0x22El -> "MinorVersion", | |
0x230l -> "NameIdentifier", | |
0x232l -> "Format", | |
0x234l -> "NameQualifier", | |
0x236l -> "Namespace", | |
0x238l -> "NotBefore", | |
0x23Al -> "NotOnOrAfter", | |
0x23Cl -> "saml", | |
0x23El -> "Statement", | |
0x240l -> "Subject", | |
0x242l -> "SubjectConfirmation", | |
0x244l -> "SubjectConfirmationData", | |
0x246l -> "ConfirmationMethod", | |
0x248l -> "urn:oasis:names:tc:SAML:1.0:cm:holder-of-key", | |
0x24Al -> "urn:oasis:names:tc:SAML:1.0:cm:sender-vouches", | |
0x24Cl -> "SubjectLocality", | |
0x24El -> "DNSAddress", | |
0x250l -> "IPAddress", | |
0x252l -> "SubjectStatement", | |
0x254l -> "urn:oasis:names:tc:SAML:1.0:am:unspecified", | |
0x256l -> "xmlns", | |
0x258l -> "Resource", | |
0x25Al -> "UserName", | |
0x25Cl -> "urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName", | |
0x25El -> "EmailName", | |
0x260l -> "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", | |
0x262l -> "u", | |
0x264l -> "ChannelInstance", | |
0x266l -> "http://schemas.microsoft.com/ws/2005/02/duplex", | |
0x268l -> "Encoding", | |
0x26Al -> "MimeType", | |
0x26Cl -> "CarriedKeyName", | |
0x26El -> "Recipient", | |
0x270l -> "EncryptedKey", | |
0x272l -> "KeyReference", | |
0x274l -> "e", | |
0x276l -> "http://www.w3.org/2001/04/xmlenc#Element", | |
0x278l -> "http://www.w3.org/2001/04/xmlenc#Content", | |
0x27Al -> "KeyName", | |
0x27Cl -> "MgmtData", | |
0x27El -> "KeyValue", | |
0x280l -> "RSAKeyValue", | |
0x282l -> "Modulus", | |
0x284l -> "Exponent", | |
0x286l -> "X509Data", | |
0x288l -> "X509IssuerSerial", | |
0x28Al -> "X509IssuerName", | |
0x28Cl -> "X509SerialNumber", | |
0x28El -> "X509Certificate", | |
0x290l -> "AckRequested", | |
0x292l -> "http://schemas.xmlsoap.org/ws/2005/02/rm/AckRequested", | |
0x294l -> "AcksTo", | |
0x296l -> "Accept", | |
0x298l -> "CreateSequence", | |
0x29Al -> "http://schemas.xmlsoap.org/ws/2005/02/rm/CreateSequence", | |
0x29Cl -> "CreateSequenceRefused", | |
0x29El -> "CreateSequenceResponse", | |
0x2A0l -> "http://schemas.xmlsoap.org/ws/2005/02/rm/CreateSequenceResponse", | |
0x2A2l -> "FaultCode", | |
0x2A4l -> "InvalidAcknowledgement", | |
0x2A6l -> "LastMessage", | |
0x2A8l -> "http://schemas.xmlsoap.org/ws/2005/02/rm/LastMessage", | |
0x2AAl -> "LastMessageNumberExceeded", | |
0x2ACl -> "MessageNumberRollover", | |
0x2AEl -> "Nack", | |
0x2B0l -> "netrm", | |
0x2B2l -> "Offer", | |
0x2B4l -> "r", | |
0x2B6l -> "SequenceFault", | |
0x2B8l -> "SequenceTerminated", | |
0x2BAl -> "TerminateSequence", | |
0x2BCl -> "http://schemas.xmlsoap.org/ws/2005/02/rm/TerminateSequence", | |
0x2BEl -> "UnknownSequence", | |
0x2C0l -> "http://schemas.microsoft.com/ws/2006/02/tx/oletx", | |
0x2C2l -> "oletx", | |
0x2C4l -> "OleTxTransaction", | |
0x2C6l -> "PropagationToken", | |
0x2C8l -> "http://schemas.xmlsoap.org/ws/2004/10/wscoor", | |
0x2CAl -> "wscoor", | |
0x2CCl -> "CreateCoordinationContext", | |
0x2CEl -> "CreateCoordinationContextResponse", | |
0x2D0l -> "CoordinationContext", | |
0x2D2l -> "CurrentContext", | |
0x2D4l -> "CoordinationType", | |
0x2D6l -> "RegistrationService", | |
0x2D8l -> "Register", | |
0x2DAl -> "RegisterResponse", | |
0x2DCl -> "ProtocolIdentifier", | |
0x2DEl -> "CoordinatorProtocolService", | |
0x2E0l -> "ParticipantProtocolService", | |
0x2E2l -> "http://schemas.xmlsoap.org/ws/2004/10/wscoor/CreateCoordinationContext", | |
0x2E4l -> "http://schemas.xmlsoap.org/ws/2004/10/wscoor/CreateCoordinationContextResponse", | |
0x2E6l -> "http://schemas.xmlsoap.org/ws/2004/10/wscoor/Register", | |
0x2E8l -> "http://schemas.xmlsoap.org/ws/2004/10/wscoor/RegisterResponse", | |
0x2EAl -> "http://schemas.xmlsoap.org/ws/2004/10/wscoor/fault", | |
0x2ECl -> "ActivationCoordinatorPortType", | |
0x2EEl -> "RegistrationCoordinatorPortType", | |
0x2F0l -> "InvalidState", | |
0x2F2l -> "InvalidProtocol", | |
0x2F4l -> "InvalidParameters", | |
0x2F6l -> "NoActivity", | |
0x2F8l -> "ContextRefused", | |
0x2FAl -> "AlreadyRegistered", | |
0x2FCl -> "http://schemas.xmlsoap.org/ws/2004/10/wsat", | |
0x2FEl -> "wsat", | |
0x300l -> "http://schemas.xmlsoap.org/ws/2004/10/wsat/Completion", | |
0x302l -> "http://schemas.xmlsoap.org/ws/2004/10/wsat/Durable2PC", | |
0x304l -> "http://schemas.xmlsoap.org/ws/2004/10/wsat/Volatile2PC", | |
0x306l -> "Prepare", | |
0x308l -> "Prepared", | |
0x30Al -> "ReadOnly", | |
0x30Cl -> "Commit", | |
0x30El -> "Rollback", | |
0x310l -> "Committed", | |
0x312l -> "Aborted", | |
0x314l -> "Replay", | |
0x316l -> "http://schemas.xmlsoap.org/ws/2004/10/wsat/Commit", | |
0x318l -> "http://schemas.xmlsoap.org/ws/2004/10/wsat/Rollback", | |
0x31Al -> "http://schemas.xmlsoap.org/ws/2004/10/wsat/Committed", | |
0x31Cl -> "http://schemas.xmlsoap.org/ws/2004/10/wsat/Aborted", | |
0x31El -> "http://schemas.xmlsoap.org/ws/2004/10/wsat/Prepare", | |
0x320l -> "http://schemas.xmlsoap.org/ws/2004/10/wsat/Prepared", | |
0x322l -> "http://schemas.xmlsoap.org/ws/2004/10/wsat/ReadOnly", | |
0x324l -> "http://schemas.xmlsoap.org/ws/2004/10/wsat/Replay", | |
0x326l -> "http://schemas.xmlsoap.org/ws/2004/10/wsat/fault", | |
0x328l -> "CompletionCoordinatorPortType", | |
0x32Al -> "CompletionParticipantPortType", | |
0x32Cl -> "CoordinatorPortType", | |
0x32El -> "ParticipantPortType", | |
0x330l -> "InconsistentInternalState", | |
0x332l -> "mstx", | |
0x334l -> "Enlistment", | |
0x336l -> "protocol", | |
0x338l -> "LocalTransactionId", | |
0x33Al -> "IsolationLevel", | |
0x33Cl -> "IsolationFlags", | |
0x33El -> "Description", | |
0x340l -> "Loopback", | |
0x342l -> "RegisterInfo", | |
0x344l -> "ContextId", | |
0x346l -> "TokenId", | |
0x348l -> "AccessDenied", | |
0x34Al -> "InvalidPolicy", | |
0x34Cl -> "CoordinatorRegistrationFailed", | |
0x34El -> "TooManyEnlistments", | |
0x350l -> "Disabled", | |
0x352l -> "ActivityId", | |
0x354l -> "http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics", | |
0x356l -> "http://docs.oasis-open.org/wss/oasis-wss-kerberos-token-profile-1.1#Kerberosv5APREQSHA1", | |
0x358l -> "http://schemas.xmlsoap.org/ws/2002/12/policy", | |
0x35Al -> "FloodMessage", | |
0x35Cl -> "LinkUtility", | |
0x35El -> "Hops", | |
0x360l -> "http://schemas.microsoft.com/net/2006/05/peer/HopCount", | |
0x362l -> "PeerVia", | |
0x364l -> "http://schemas.microsoft.com/net/2006/05/peer", | |
0x366l -> "PeerFlooder", | |
0x368l -> "PeerTo", | |
0x36Al -> "http://schemas.microsoft.com/ws/2005/05/routing", | |
0x36Cl -> "PacketRoutable", | |
0x36El -> "http://schemas.microsoft.com/ws/2005/05/addressing/none", | |
0x370l -> "http://schemas.microsoft.com/ws/2005/05/envelope/none", | |
0x372l -> "http://www.w3.org/2001/XMLSchema-instance", | |
0x374l -> "http://www.w3.org/2001/XMLSchema", | |
0x376l -> "nil", | |
0x378l -> "type", | |
0x37Al -> "char", | |
0x37Cl -> "boolean", | |
0x37El -> "byte", | |
0x380l -> "unsignedByte", | |
0x382l -> "short", | |
0x384l -> "unsignedShort", | |
0x386l -> "int", | |
0x388l -> "unsignedInt", | |
0x38Al -> "long", | |
0x38Cl -> "unsignedLong", | |
0x38El -> "float", | |
0x390l -> "double", | |
0x392l -> "decimal", | |
0x394l -> "dateTime", | |
0x396l -> "string", | |
0x398l -> "base64Binary", | |
0x39Al -> "anyType", | |
0x39Cl -> "duration", | |
0x39El -> "guid", | |
0x3A0l -> "anyURI", | |
0x3A2l -> "QName", | |
0x3A4l -> "time", | |
0x3A6l -> "date", | |
0x3A8l -> "hexBinary", | |
0x3AAl -> "gYearMonth", | |
0x3ACl -> "gYear", | |
0x3AEl -> "gMonthDay", | |
0x3B0l -> "gDay", | |
0x3B2l -> "gMonth", | |
0x3B4l -> "integer", | |
0x3B6l -> "positiveInteger", | |
0x3B8l -> "negativeInteger", | |
0x3BAl -> "nonPositiveInteger", | |
0x3BCl -> "nonNegativeInteger", | |
0x3BEl -> "normalizedString", | |
0x3C0l -> "ConnectionLimitReached", | |
0x3C2l -> "http://schemas.xmlsoap.org/soap/envelope/", | |
0x3C4l -> "actor", | |
0x3C6l -> "faultcode", | |
0x3C8l -> "faultstring", | |
0x3CAl -> "faultactor", | |
0x3CCl -> "detail" | |
) | |
} | |
private class WCFBinaryXml(is: InputStream, os: OutputStream, stringDictionary: Map[Long, String] = WCFBinaryXml.dictionaryMap) extends Logging { | |
private val bis: BufferedInputStream = new BufferedInputStream(is) | |
private val writer: PrintStream = new PrintStream(os) | |
private def dictionaryString(code: Long): String = stringDictionary(code) | |
// Conversion Helpers | |
private def makeDecimal(bytes: Array[Byte]): String = { | |
/* | |
typedef struct tagDEC { | |
WORD wReserved; | |
BYTE scale; | |
BYTE sign; | |
ULONG Hi32; | |
ULONGLONG Lo64; | |
} DECIMAL; | |
*/ | |
val buffer: ByteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN) | |
val wReserved = buffer.getShort() // Increment | |
val scale: Int = buffer.get().toInt | |
val sign: Int = if(buffer.get().toInt < 0) -1 else 1 | |
val hi32: BigInt = BigInt(UInt(buffer.getInt()).toString) | |
val lo64: BigInt = BigInt(ULong(buffer.getLong).toString) | |
val bi: BigInt = (sign * (hi32 << 64)) + lo64 | |
BigDecimal(bi, scale).toString() | |
} | |
private def makeDate(bytes: Array[Byte]): String = { | |
// Value (62 bits): The 62-bit unsigned integer value that specifies the number of 100 nanoseconds that had elapsed since 12:00:00, January 1, 0001. | |
// The value can represent time instants in a granularity of 100 nanoseconds until 23:59:59.9999999, December 31, 9999. | |
// The value MUST be less than the decimal value 3155378976000000000. | |
// TZ (2 bits): A two-bit unsigned integer that contains TimeZone information. This MUST be 0, 1, or 2. | |
val tz: Int = bytes.last & 0x03 | |
// Ignore the TZ | |
val ticks: Long = ByteBuffer.wrap( bytes ).order(ByteOrder.LITTLE_ENDIAN).getLong() | |
val epoch: Long = (ticks - 621355968000000000l) / 100 | |
val fraction: Long = (epoch % 10000000) | |
val date: Date = new Date( (epoch / 100l) ) | |
val formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss") | |
formatter.setTimeZone(TimeZone.getTimeZone("UTC")) // Right now still ignoring what the TimeZone value is and just assuming UTC TimeZone | |
// If TZ is one, then the time is in UTC (Coordinated Universal Time), and the date MUST be interpreted as having a trailing character "Z". | |
// If TZ is two, then the time is a local time, and the date MUST be interpreted as having additional characters that indicate the UTC offset. The UTC offset MUST be the time zone offset in which the document is being decoded. | |
formatter.format(date)+ (if(fraction > 0) s".$fraction" else "") | |
} | |
private def makeTimeSpan(bytes: Array[Byte]): String = { | |
// Value (8 bytes): A 64-bit signed integer value that specifies a duration in 100 nanosecond units. The values range from -10675199 days, 2 hours, 48 minutes, and 05.4775808 seconds to 10675199 days, 2 hours, 48 minutes, and 05.4775807 seconds. | |
val span: Long = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getLong() | |
// -3440000000 -PT5M44S | |
// 120000000000 PT3H20M | |
(for { | |
isNegative: Boolean <- Some(span < 0) | |
fraction: Long <- Some(span % 10000000) | |
secondsSpan: Long <- Some(span / 10000000) | |
seconds: Long <- Some(secondsSpan % 60) | |
minutesSpan: Long <- Some(secondsSpan / 60) | |
minutes: Long <- Some(minutesSpan % 60) | |
hoursSpan: Long <- Some(minutesSpan / 60) | |
hours <- Some(hoursSpan % 24) | |
daysSpan: Long <- Some(hoursSpan / 24) | |
days: Long <- Some(daysSpan) | |
} yield { | |
val timeSpanString = new StringBuilder | |
if(isNegative) timeSpanString.append("-") | |
timeSpanString.append("PT") | |
if(days != 0) timeSpanString.append(days.abs.toString) | |
if(hours != 0) timeSpanString.append(s"${hours.abs}H") | |
if(minutes != 0) timeSpanString.append(s"${minutes.abs}M") | |
if(seconds != 0) timeSpanString.append(s"${seconds.abs}S") | |
timeSpanString.result | |
}).getOrElse("") | |
} | |
private def makeUrn(bytes: Array[Byte]): String = s"urn:uuid:${makeUuid(bytes)}" | |
private def makeUuid(bytes: Array[Byte]): String = { | |
val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN) | |
/* | |
unsigned32 time_low; | |
unsigned16 time_mid; | |
unsigned16 time_hi_and_version; | |
unsigned8 clock_seq_hi_and_reserved; | |
unsigned8 clock_seq_low; | |
byte node[6]; | |
*/ | |
"%08x".format(buffer.getInt()) + | |
"-" + | |
"%04x".format(buffer.getShort()) + | |
"-" + | |
"%04x".format(buffer.getShort()) + | |
"-" + | |
"%02x".format(buffer.get()) + | |
"%02x".format(buffer.get()) + | |
"-" + | |
"%02x".format(buffer.get()) + | |
"%02x".format(buffer.get()) + | |
"%02x".format(buffer.get()) + | |
"%02x".format(buffer.get()) + | |
"%02x".format(buffer.get()) + | |
"%02x".format(buffer.get()) | |
} | |
// InputStream Reading | |
// private def readByte8(): Byte = bis.read().toByte | |
// Signed Int | |
private def readInt8(): Int = bis.read().toByte.toInt | |
// Unsigned Int | |
private def readUInt8(): Int = (bis.read() & 0xff) | |
// Peak at the next signed int | |
private def peakInt8(): Int = { | |
bis.mark(1) | |
val nextInt: Int = bis.read() | |
bis.reset() | |
nextInt | |
} | |
// Peak at the Next Unsigned Int | |
private def peakUInt8(): Int = (peakInt8 & 0xff) | |
@tailrec private def readMultiByteInt31(power: Int = 1, result: Int = 0): Int = { | |
val byte: Int = readInt8 | |
val newResult: Int = result + (power * (byte & 0x7f)) | |
if( (byte & 0x80) == 0x80 ) readMultiByteInt31(power * (2**7), newResult ) | |
else newResult | |
} | |
private def readBytes(size: Int): Array[Byte] = { | |
(0 until size).map { _ => bis.read().toByte }.toArray | |
} | |
// Support for the Primitive String Type | |
private def readString(): String = { | |
val len: Int = readMultiByteInt31() | |
val str = new String(readBytes(len), "UTF-8") | |
str | |
} | |
private def readText(): String = { | |
val textType: Int = readUInt8 | |
readText(textType) | |
} | |
private def readText(tpe: Int): String = { | |
val text: String = tpe match { | |
case 0x80 => "0" // ZeroText | |
case 0x82 => "1" // OneText | |
case 0x84 => "false" // FalseText | |
case 0x86 => "true" // TrueText | |
case 0x88 => // Int8Text | |
// Value (1 byte): The signed 8-bit integer value. | |
readInt8.toString | |
case 0x8A => // Int16Text | |
// Value (2 bytes): The signed 16-bit integer value. | |
ByteBuffer.wrap(readBytes(2)).order(ByteOrder.LITTLE_ENDIAN).getShort().toString | |
case 0x8C => // Int32Text | |
// Value (4 bytes): The signed 32-bit integer value. | |
ByteBuffer.wrap(readBytes(4)).order(ByteOrder.LITTLE_ENDIAN).getInt().toString | |
case 0x8E => // Int64Text | |
// Value (8 bytes): The signed 64-bit integer value. | |
ByteBuffer.wrap(readBytes(8)).order(ByteOrder.LITTLE_ENDIAN).getLong().toString | |
case 0x90 => // FloatText | |
// Value (4 bytes): The 32-bit single precision floating point value as described in [IEEE754]. | |
ByteBuffer.wrap(readBytes(4)).order(ByteOrder.LITTLE_ENDIAN).getFloat().toString | |
case 0x92 => // DoubleText | |
// Value (8 bytes): The 64-bit single precision floating point value as specified in [IEEE754]. | |
ByteBuffer.wrap(readBytes(8)).order(ByteOrder.LITTLE_ENDIAN).getDouble().toString | |
case 0x94 => // DecimalText | |
makeDecimal(readBytes(16)) | |
case 0x96 => // DateTimeText | |
makeDate(readBytes(8)) | |
case 0x98 => // Chars8Text | |
// Length (1 byte): This is the length in bytes of the UTF-8 [RFC2279]-encoded string and is represented as UINT8. | |
val length: Int = readUInt8 | |
new String(readBytes(length), "UTF-8") | |
case 0x9A => // Chars16Text | |
// Length (2 bytes): This is the length in bytes of the UTF-8 [RFC2279]-encoded string and is represented as UINT16. | |
val length: UShort = UShort(ByteBuffer.wrap(readBytes(2)).order(ByteOrder.LITTLE_ENDIAN).getShort()) | |
new String(readBytes(length.toInt), "UTF-8") | |
case 0x9C => // Chars32Text | |
// Length (4 bytes): This is the length in bytes of the string when encoded in UTF-8, as specified in [RFC2279], and is represented as INT32. The value of Length MUST be positive. | |
val length: Int = ByteBuffer.wrap(readBytes(4)).order(ByteOrder.LITTLE_ENDIAN).getInt() | |
new String(readBytes(length), "UTF-8") | |
case 0x9E => // Bytes8Text | |
val length: Int = readUInt8 | |
Base64.encodeBytes(readBytes(length)) | |
case 0xA0 => // Bytes16Text | |
// Length (2 bytes): This is the length in bytes of the binary data and is represented as UINT16. | |
val length: UShort = UShort(ByteBuffer.wrap(readBytes(2)).order(ByteOrder.LITTLE_ENDIAN).getShort()) | |
Base64.encodeBytes(readBytes(length.toInt)) | |
case 0xA2 => // Bytes32Text | |
// Length (4 bytes): This is the length in bytes of the binary data and is represented as INT32. The value of Length MUST be positive. | |
val length: Int = ByteBuffer.wrap(readBytes(4)).order(ByteOrder.LITTLE_ENDIAN).getInt() | |
Base64.encodeBytes(readBytes(length)) | |
case 0xA8 => "" // EmptyText | |
case 0xAA => dictionaryString(readMultiByteInt31()) // DictionaryText | |
case 0xAC => makeUrn(readBytes(16)) // UniqueIdText | |
case 0xAE => makeTimeSpan(readBytes(8)) // TimeSpanText | |
case 0xB0 => makeUuid(readBytes(16)) // UuidText | |
case 0xB2 => // UInt64Text | |
// Value (8 bytes): The unsigned 64-bit integer value. | |
(new ULong(ByteBuffer.wrap(readBytes(8)).order(ByteOrder.LITTLE_ENDIAN).getLong())).toString | |
case 0xB4 => (readUInt8 != 0).toString // BoolText | |
case 0xB6 => // UnicodeChars8Text | |
val length: Int = readUInt8 | |
new String(readBytes(length), "UTF-16") | |
case 0xB8 => // UnicodeChars16Text | |
val length: UShort = UShort(ByteBuffer.wrap(readBytes(2)).order(ByteOrder.LITTLE_ENDIAN).getShort()) | |
new String(readBytes(length.toInt), "UTF-16") | |
case 0xBA => // UnicodeChars32Text | |
//Length(variable): This is the length in bytes of the string when encoded in UTF-16,as specified in [RFC2781], and MUST be encoded using MultiByteInt31. | |
//Bytes (variable): The string encoded as UTF-16 [RFC2781] bytes. | |
val length: UInt = UInt(ByteBuffer.wrap(readBytes(4)).order(ByteOrder.LITTLE_ENDIAN).getInt()) | |
// Docs say use readMultiByteInt31(), but it appears to be a normal 4 byte unsigned int instead.... | |
new String(readBytes(length.toInt), "UTF-16") | |
case 0xBC => // QNameDictionaryText | |
val letter: Char = ('a'.toInt + readInt8).toChar | |
val word: String = dictionaryString(readMultiByteInt31()) | |
s"$letter:$word" | |
case 0xA4 => // StartListText | |
// StartListText / EndListText Records (0xA4, 0xA6) | |
val firstValue = readUInt8 | |
val sb = new StringBuilder | |
if(firstValue != 0xA6) { | |
sb.append(readText(firstValue)) | |
while(peakUInt8() != 0xA6) { | |
val nextValue: Int = readUInt8() | |
sb.append(" " + readText(nextValue)) | |
} | |
} | |
if(peakUInt8() == 0xA6) readUInt8() // pop off | |
sb.result | |
// This structure represents attribute or element content. These records identify the start and end of a list of text records separated by a single whitespace character. They have no additional fields. The records that they bracket MUST be text records and MUST NOT contain a StartListText or EndListText record. An EndListText record MUST have a corresponding StartListText record. | |
/*return new BinaryXml("").decode(content);*/ | |
case _ => throw new Exception(s"Unmatched hex text type found: ${Integer.toHexString(tpe)}") | |
} | |
text | |
} | |
// XML Escape Helpers | |
private object Escapes { | |
private val attributePairs = List( | |
"lt" -> '<', | |
"quot" -> '"' | |
) | |
val attributeEscapeMap = Map((for ((s, c) <- attributePairs) yield (c, "&%s;" format s)) : _*) | |
private val elementPairs = List( | |
"lt" -> '<', | |
"gt" -> '>', | |
"amp" -> '&', | |
"quot" -> '"' | |
) | |
val elementEscapeMap = Map((for ((s, c) <- elementPairs) yield (c, "&%s;" format s)) : _*) | |
} | |
private def sbToString(f: (StringBuilder) => Unit): String = { | |
val sb = new StringBuilder | |
f(sb) | |
sb.toString | |
} | |
private def escape(text: String): String = sbToString(escape(text, _)) | |
private def escape(text: String, s: StringBuilder): StringBuilder = | |
text.foldLeft(s)((s, c) => Escapes.elementEscapeMap.get(c) match { | |
case Some(str) => s append str | |
case None => s append c | |
}) | |
final def escapeAttribute(text: String): String = sbToString(escapeAttribute(text, _)) | |
final def escapeAttribute(text: String, s: StringBuilder): StringBuilder = | |
text.foldLeft(s)((s, c) => Escapes.attributeEscapeMap.get(c) match { | |
case Some(str) => s append str | |
case None => s append c | |
}) | |
// Identify Type of Records | |
private def isElement(tpe: Int): Boolean = (tpe >= 0x40 && tpe <= 0x77) // "See the following tables from 0x40 to 0x77 inclusive." | |
private def isAttribute(tpe: Int): Boolean = (tpe >= 0x04 && tpe <= 0x3F) // "An attribute record is any record with a record type (see Table 1) from 0x04 to 0x3F inclusive." | |
private def isTextRecord(tpe: Int): Boolean = (tpe >= 0x80 && tpe <= 0xBD) // "A text record is any record with a record type (see Table 1) from 0x80 to 0xBD inclusive." | |
private def isReserved(tpe: Int): Boolean = ( (tpe >= 0xBE && tpe <= 0xBF) || (tpe >= 0xFE && tpe <= 0xFF) ) | |
// Decode Attributes | |
private def decodeAttributesString(): String = { | |
decodeAttributes().mkString(" ").toBlankOption.map { attrs: String => s" $attrs" }.getOrElse("") | |
} | |
@tailrec private def decodeAttributes(previousAttributes: Seq[String] = Nil): Seq[String] = { | |
if(!isAttribute(peakUInt8)) return Nil | |
val attributeType: Int = readUInt8 | |
val attributeString: String = attributeType match { | |
case 0x04 => // ShortAttribute | |
val name: String = readString // Name (variable): The name of the attribute encoded using String. The length of this String MUST be nonzero. The name MUST NOT be "xmlns". | |
val value: String = escapeAttribute(readText) // Value (variable): The value of the attribute encoded using a text record. | |
s"""$name="$value"""" | |
case 0x05 => // Attribute | |
val prefix: String = readString // Prefix (variable): The prefix of the attribute encoded using String. The length of this String MUST be nonzero. The prefix MUST NOT be "xmlns". | |
val name: String = readString // Name (variable): The name of the attribute encoded using String. The length of this String MUST be nonzero. The name MUST NOT be "xmlns". | |
val value: String = escapeAttribute(readText) // Value (variable): The value of the attribute encoded using a single text record (Text Records). | |
s"""$prefix:$name="$value"""" | |
case 0x06 => // ShortDictionaryAttribute | |
val nameCode: Int = readMultiByteInt31() // Name (variable): The name of the attribute encoded using DictionaryString. The length of this String MUST be nonzero. The name MUST NOT be "xmlns". | |
val value: String = escapeAttribute(readText) // Value (variable): The value of the attribute encoded using a text record. | |
s"""${dictionaryString(nameCode)}="$value"""" | |
case 0x07 => // DictionaryAttribute | |
val prefix: String = readString // Prefix (variable): The prefix of the attribute encoded using String. The length of this String MUST be nonzero. The prefix MUST NOT be "xmlns". | |
val nameCode: Int = readMultiByteInt31() // Name (variable): The name of the attribute encoded using DictionaryString. The length of this String MUST be nonzero. The name MUST NOT be "xmlns". | |
val value: String = escapeAttribute(readText) // Value (variable): The value of the attribute encoded using a text record. | |
s"""$prefix:${dictionaryString(nameCode)}="$value"""" | |
case 0x08 => // ShortXmlnsAttribute | |
val value: String = escapeAttribute(readString) // Value (variable): The value of the attribute encoded using String. | |
s"""xmlns="$value"""" | |
case 0x09 => // XmlnsAttribute | |
val prefix: String = readString // Prefix (variable): The prefix of the attribute encoded using String. The length of this String MUST be nonzero. The prefix MUST NOT be "xmlns". | |
val value: String = escapeAttribute(readString) // Value (variable): The value of the attribute encoded using String. | |
s"""xmlns:$prefix="$value"""" | |
case 0x0A => // ShortDictionaryXmlnsAttribute | |
val nameCode: Int = readMultiByteInt31() // Value (variable): The value of the attribute encoded using DictionaryString. | |
s"""xmlns="${dictionaryString(nameCode)}"""" | |
case 0x0B => // DictionaryXmlnsAttribute | |
val prefix: String = readString // Prefix (variable): The prefix of the attribute encoded using String. The length of this String MUST be nonzero. The prefix MUST NOT be "xmlns". | |
val nameCode: Int = readMultiByteInt31() // Value (variable): The value of the attribute encoded using DictionaryString. | |
s"""xmlns:$prefix="${dictionaryString(nameCode)}"""" | |
case prefixDictionaryAttributeCode: Int if(prefixDictionaryAttributeCode >= 0x0C && prefixDictionaryAttributeCode <= 0x25) => | |
// PrefixDictionaryAttribute[A-Z] | |
val nameCode: Int = readMultiByteInt31() // Name (variable): The name of the attribute encoded using DictionaryString. The length of this String MUST be nonzero. The name MUST NOT be "xmlns". | |
val value: String = escapeAttribute(readText) // Value (variable): The value of the attribute encoded using a text record. | |
val letter: Char = ('a' + (prefixDictionaryAttributeCode - 0x0C)).toChar | |
s"""$letter:${dictionaryString(nameCode)}="$value"""" | |
case prefixAttributeCode: Int if(prefixAttributeCode >= 0x26 && prefixAttributeCode <= 0x3F) => | |
// PrefixAttribute[A-Z] | |
val name: String = readString // Name (variable): The name of the attribute encoded using String. The length of this String MUST be nonzero. The name MUST NOT be "xmlns". | |
val value: String = escapeAttribute(readText) // Value (variable): The value of the attribute encoded using text record. | |
val letter: Char = ('a' + (prefixAttributeCode - 0x26)).toChar | |
s"""$letter:$name="$value"""" | |
} | |
val attrs: Seq[String] = previousAttributes :+ attributeString | |
if(isAttribute(peakUInt8)) decodeAttributes(attrs) | |
else attrs | |
} | |
// Decode Elements | |
case class Element(val beginTag: String, val endTag: String) | |
def decodeElement(): Element = { | |
val elementType: Int = readUInt8 | |
decodeElement(elementType) | |
} | |
def decodeElement(tpe: Int): Element = { | |
require(isElement(tpe)) | |
val element: Element = tpe match { | |
case 0x40 => // ShortElement | |
val name = readString // Name (variable): The name of the element encoded using String. The length of this String MUST be nonzero. The name MUST NOT be "xmlns". | |
Element(s"<$name${decodeAttributesString}>", s"</$name>") | |
case 0x41 => // Element | |
// Attributes (variable): Zero or more attribute records. | |
val prefix: String = readString // Prefix (variable): The prefix of the element encoded using String. The length of this String MUST be nonzero. The name MUST NOT be "xmlns". | |
val name: String = readString // Name (variable): The name of the element encoded using String. The length of this String MUST be nonzero. The name MUST NOT be "xmlns". | |
Element(s"<$prefix:$name${decodeAttributesString}>", s"</$prefix:$name>") | |
case 0x42 => // ShortDictionaryElement | |
val nameCode: Int = readMultiByteInt31() // Name (variable): The name of the element encoded using DictionaryString. The length of this String MUST be nonzero. The name MUST NOT be "xmlns". | |
val name: String = dictionaryString(nameCode) | |
Element(s"<$name${decodeAttributesString}>", s"</$name>") | |
case 0x43 => // DictionaryElement | |
val prefix: String = readString // Prefix (variable): The prefix of the element encoded using String. The length of this String MUST be nonzero. The name MUST NOT be "xmlns". | |
val nameCode: Int = readMultiByteInt31() // Name (variable): The name of the element encoded using Dictionary. The length of this String MUST be nonzero. The name MUST NOT be "xmlns". | |
val name: String = dictionaryString(nameCode) | |
Element(s"<$prefix:$name${decodeAttributesString}>", s"</$prefix:$name>") | |
case prefixDictionaryElementCode: Int if( prefixDictionaryElementCode >= 0x44 && prefixDictionaryElementCode <= 0x5D) => | |
// PrefixDictionaryElement[A-Z] | |
val nameCode: Int = readMultiByteInt31() // Name (variable): The name of the element encoded using DictionaryString. The length of this String MUST be nonzero. The name MUST NOT be "xmlns". | |
val letter: Char = ('a' + (prefixDictionaryElementCode - 0x44)).toChar | |
val name: String = dictionaryString(nameCode) | |
Element(s"<$letter:$name${decodeAttributesString}>", s"</$letter:$name>") | |
case prefixElementCode: Int if(prefixElementCode >= 0x5E && prefixElementCode <= 0x77) => | |
// PrefixElement[A-Z] | |
val name: String = readString // Name (variable): The name of the element encoded using String. The length of this String MUST be nonzero. The name MUST NOT be "xmlns". | |
val letter: Char = ('a' + (prefixElementCode - 0x5E)).toChar | |
Element(s"<$letter:$name${decodeAttributesString}>", s"</$letter:$name>") | |
} | |
element | |
} | |
// TODO: I really don't want to have to use a mutable variable to keep track of the closing tags | |
// can we re-write this to have @tailrec on decode? | |
private val endTagStack: scala.collection.mutable.Stack[String] = new scala.collection.mutable.Stack[String] | |
def decode(): Unit = synchronized { | |
assert(bis.available() > 0, "Has this inputstream been already used?") | |
// Reset Stack/Wrapping | |
endTagStack.clear() | |
while(peakInt8() != -1) { | |
decodeHelper | |
} | |
} | |
private def printTabs(size: Int, startNewLine: Boolean = false): Unit = { | |
writer.print((if(startNewLine) "\n" else "") + ("\t" * size)) | |
} | |
private def decodeHelper(): Unit = { | |
val recordType: Int = readUInt8 | |
recordType match { | |
case 0x01 => // EndElement | |
val endTag: String = endTagStack.pop | |
writer.println(endTag) | |
case 0x02 => // Comment | |
val comment: String = s"<!--${readString}-->" | |
printTabs(endTagStack.size) | |
writer.println(comment) | |
case 0x03 => // Array | |
// Element (variable): An Element record. | |
val element: Element = decodeElement | |
// End Element (1 byte): An EndElement record. | |
val endElement: Int = readUInt8 | |
assert(endElement == 0x01) | |
// Record Type (1 byte): The record type of the element content. This MUST be one of the values in the following table. | |
val arrayRecordType: Int = (readUInt8 - 1); // simpler than reading & then applying below | |
/* | |
val recordType: Int = arrayRecordType match { | |
case 0xB5 => 0xB4 // BoolTextWithEndElement | |
case 0x8B => 0x8A // Int16TextWithEndElement | |
case 0x8D => 0x8C // Int32TextWithEndElement | |
case 0x8F => 0x8E // Int64TextWithEndElement | |
case 0x91 => 0x90 // FloatTextWithEndElement | |
case 0x93 => 0x92 // DoubleTextWithEndElement | |
case 0x95 => 0x94 // DecimalTextWithEndElement | |
case 0x97 => 0x97 // DateTimeTextWithEndElement | |
case 0xAF => 0xAE // TimeSpanTextWithEndElement | |
case 0xB1 => 0xB0 // UuidTextWithEndElement | |
}*/ | |
// Length(variable): Thenumberofelements,encodedwithMultiByteInt31.ThisMUSTnotbe zero. | |
val length: Int = readMultiByteInt31() | |
(1 to length).foreach { seq: Int => | |
val recordString: String = s"${element.beginTag}${readText(arrayRecordType)}${element.endTag}" | |
printTabs(endTagStack.size) | |
writer.println(recordString) | |
} | |
case textRecordType: Int if (isTextRecord(textRecordType)) => | |
val hasEndElement: Boolean = (textRecordType % 2) == 1 | |
val canonicalTextRecordType: Int = if (hasEndElement) textRecordType - 1 else textRecordType | |
val text: String = readText(canonicalTextRecordType) | |
writer.print(escape(text)) | |
if (hasEndElement) { | |
val endTag: String = endTagStack.pop | |
writer.print(endTag) | |
} | |
case elementRecordType: Int if(isElement(elementRecordType)) => | |
val element: Element = decodeElement(recordType) | |
if(endTagStack.size > 0) printTabs(endTagStack.size, startNewLine = true) | |
writer.print(element.beginTag) | |
endTagStack.push(element.endTag) | |
case reservedType: Int if(isReserved(reservedType)) => | |
logger.warn(s"Got a reserved code: $reservedType...that's kinda weird") | |
case unknownRecordType: Int if (!isElement(unknownRecordType)) => | |
throw new Exception(s"unknownRecordType: ${"%02x".format(unknownRecordType)}") | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment