Skip to content

Instantly share code, notes, and snippets.

@er1c
Last active August 29, 2015 14:11
Show Gist options
  • Save er1c/0e792017d9d03be4bc22 to your computer and use it in GitHub Desktop.
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
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>""") }
}
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