You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by sl...@apache.org on 2019/09/05 14:26:35 UTC

[incubator-daffodil] branch master updated: Add blob support

This is an automated email from the ASF dual-hosted git repository.

slawrence pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-daffodil.git


The following commit(s) were added to refs/heads/master by this push:
     new 9de0d1a  Add blob support
9de0d1a is described below

commit 9de0d1a3c3d294600d5c769243efe4f67b03bac1
Author: Steve Lawrence <sl...@apache.org>
AuthorDate: Thu Aug 15 13:02:34 2019 -0400

    Add blob support
    
    - Adds support for xs:anyURI primitive type. Elements with this type
      must specifiy the new property dfdl:objectKind, which can be
      "bytes" or "characters". Support for "characters" is not added in this
      patch.
    - On parse elements with objectKind="bytes" will have the data read
      and written out to a file with a randomly generated name, with a URI
      to that file being put in the infoset. On unparse, the data is read
      from the URI and written to the data stream. Behavior is very similar
      to xs:hexBinary.
    - BLOB Objects must have lengths set with dfdl:lengthKind="explicit".
    - New API functions are added to set where to write blob files to and to
      a get list of all blob files that were written.
    - TDML Runner is updated to be aware of xsi:type="xs:anyURI" and compare
      the contents of the blob files. It will also delete blob files after a
      test passes.
    - The CLI will write blob files to the $PWD/daffodil-blobs/ directory.
    
    DAFFODIL-1735
---
 .github/workflows/main.yml                         |   2 +-
 .../src/main/scala/org/apache/daffodil/Main.scala  |   9 +-
 .../org/apache/daffodil/dsom/ElementBase.scala     |   8 +-
 .../apache/daffodil/dsom/RestrictionUnion.scala    |   5 +-
 .../daffodil/dsom/RuntimePropertyMixins.scala      |   8 +-
 .../org/apache/daffodil/dsom/SimpleTypes.scala     |   2 +
 .../daffodil/grammar/ElementBaseGrammarMixin.scala |  27 ++
 .../daffodil/grammar/primitives/Padded.scala       |   3 +
 .../grammar/primitives/PrimitivesLengthKind.scala  |   9 +
 .../org/apache/daffodil/io/DataInputStream.scala   |  11 +
 .../daffodil/io/InputSourceDataInputStream.scala   |  12 +
 .../io/StringDataInputStreamForUnparse.scala       |   1 +
 .../main/scala/org/apache/daffodil/util/Misc.scala |  53 +++-
 .../scala/org/apache/daffodil/xml/XMLUtils.scala   |  84 ++++-
 .../daffodil/xml/test/unit/TestXMLUtils.scala      |  78 ++++-
 .../apache/daffodil/xsd/DFDL_part2_attributes.xsd  |   1 +
 .../org/apache/daffodil/xsd/DFDL_part3_model.xsd   |   1 +
 .../resources/org/apache/daffodil/xsd/dafext.xsd   |   7 +
 .../resources/org/apache/daffodil/xsd/dfdlx.xsd    |  16 +
 .../processors/unparsers/BlobLengthUnparser.scala  | 147 +++++++++
 .../scala/org/apache/daffodil/dpath/NodeInfo.scala |  55 +++-
 .../org/apache/daffodil/infoset/InfosetImpl.scala  |   1 +
 .../apache/daffodil/infoset/InfosetOutputter.scala |  34 ++
 .../apache/daffodil/processors/DataProcessor.scala |   9 +
 .../processors/parsers/BlobLengthParsers.scala     |  92 ++++++
 .../daffodil/processors/parsers/PState.scala       |  26 +-
 .../org/apache/daffodil/tdml/TDMLRunner.scala      |  66 +++-
 .../tdml/processor/TDMLDFDLProcessor.scala         |   8 +-
 .../apache/daffodil/tdml/TDMLInfosetInputter.scala |  22 +-
 .../tdml/processor/DaffodilTDMLDFDLProcessor.scala |  37 ++-
 .../daffodil/section05/simple_types/Blobs.tdml     | 349 +++++++++++++++++++++
 .../section05/simple_types/SimpleTypes.tdml        |   3 +-
 .../section05/simple_types/blobs/blob_01.bin       |   1 +
 .../section05/simple_types/blobs/blob_02.bin       |   1 +
 .../section05/simple_types/blobs/blob_03.bin       |   1 +
 .../section05/simple_types/blobs/blob_04.bin       |   1 +
 .../section05/simple_types/blobs/blob_07.bin       |   1 +
 .../section05/simple_types/blobs/blob_13a.bin      |   1 +
 .../section05/simple_types/blobs/blob_13b.bin      |   1 +
 .../section05/simple_types/blobs/blob_13c.bin      |   1 +
 .../section05/simple_types/blobs/blob_13d.bin      |   1 +
 .../section05/simple_types/TestBlobs.scala         |  56 ++++
 42 files changed, 1176 insertions(+), 75 deletions(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index df527bd..1df154f 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -34,7 +34,7 @@ jobs:
       ############################################################
 
       - name: Checkout Repository
-        uses: actions/checkout@master
+        uses: actions/checkout@v1.0.0
 
       - name: Install Dependencies (Linux)
         run: |
diff --git a/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala b/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala
index 7b03b7a..ceed77d 100644
--- a/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala
+++ b/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala
@@ -28,6 +28,7 @@ import java.io.FileInputStream
 import java.io.ByteArrayInputStream
 import java.io.File
 import java.nio.channels.Channels
+import java.nio.file.Paths
 import java.net.URI
 import java.util.Scanner
 
@@ -720,8 +721,12 @@ object Main extends Logging {
     pf
   }
 
+  // write blobs to $PWD/daffodil-blobs/*.bin
+  val blobDir = Paths.get(System.getProperty("user.dir"), "daffodil-blobs")
+  val blobSuffix = ".bin"
+
   def getInfosetOutputter(infosetType: String, os: java.io.OutputStream): InfosetOutputter = {
-    infosetType match {
+    val outputter = infosetType match {
       case "xml" => new XMLTextInfosetOutputter(os, true)
       case "scala-xml" => new ScalaXMLInfosetOutputter()
       case "json" => new JsonInfosetOutputter(os, true)
@@ -729,6 +734,8 @@ object Main extends Logging {
       case "w3cdom" => new W3CDOMInfosetOutputter()
       case "null" => new NullInfosetOutputter()
     }
+    outputter.setBlobAttributes(blobDir, null, blobSuffix)
+    outputter
   }
 
   // Converts the data to whatever form the InfosetInputter will want. Note
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ElementBase.scala b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ElementBase.scala
index a4b0771..09d98a7 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ElementBase.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ElementBase.scala
@@ -643,6 +643,10 @@ trait ElementBase
       primType match {
         case PrimType.HexBinary => Representation.Binary
         case PrimType.String => Representation.Text
+        case PrimType.AnyURI => objectKind match {
+          case ObjectKindType.Bytes => Representation.Binary
+          case ObjectKindType.Chars => Representation.Text
+        }
         case _ => representation
       }
     } else {
@@ -650,7 +654,7 @@ trait ElementBase
     }
     rep match {
       case Representation.Binary =>
-        if (isComplexType || primType != PrimType.HexBinary) byteOrderEv // ensure defined
+        if (isComplexType || (primType != PrimType.HexBinary && primType != PrimType.AnyURI)) byteOrderEv // ensure defined
       case _ =>
         charsetEv // ensure defined
     }
@@ -680,6 +684,7 @@ trait ElementBase
 
   private def getImplicitAlignmentInBits(thePrimType: PrimType, theRepresentation: Representation): Int = {
     (theRepresentation, thePrimType) match {
+      case (Representation.Text, PrimType.AnyURI) => this.subsetError("Property value objectKind='chars' is not supported.")
       case (Representation.Text, PrimType.HexBinary) => Assert.impossible("type xs:hexBinary with representation='text'")
       case (Representation.Text, _) => knownEncodingAlignmentInBits
       case (Representation.Binary, PrimType.String) => Assert.impossible("type xs:string with representation='binary'")
@@ -687,6 +692,7 @@ trait ElementBase
       case (Representation.Binary, PrimType.Float | PrimType.Boolean) => 32
       case (Representation.Binary, PrimType.Double) => 64
       case (Representation.Binary, PrimType.HexBinary) => 8
+      case (Representation.Binary, PrimType.AnyURI) => 8
       // Handle 64 bit types
       case (Representation.Binary, PrimType.Long | PrimType.UnsignedLong) =>
         binaryNumberRep match {
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RestrictionUnion.scala b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RestrictionUnion.scala
index f49475c..b09fa05 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RestrictionUnion.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RestrictionUnion.scala
@@ -335,11 +335,11 @@ sealed trait TypeChecks { self: Restriction =>
       }
     }
 
-    // Don't need to range check String or HexBinary
+    // Don't need to range check String or HexBinary or blobs.
     // no point attempting a conversion to BigDecimal so
     // return early here.
     primType match {
-      case PrimType.String | PrimType.HexBinary => return (true, None)
+      case PrimType.String | PrimType.HexBinary | PrimType.AnyURI => return (true, None)
       case _ => /* Continue on below */
     }
 
@@ -373,6 +373,7 @@ sealed trait TypeChecks { self: Restriction =>
           case PrimType.Decimal => true // Unbounded Decimal
           case PrimType.HexBinary => Assert.impossibleCase // Handled earlier, shouldn't get here
           case PrimType.String => Assert.impossibleCase // Handled earlier, shouldn't get here
+          case PrimType.AnyURI => Assert.impossibleCase // Handled earlier, shouldn't get here
           case PrimType.NonNegativeInteger => isInNonNegativeIntegerRange(theValue)
         }
         val isValueWhole = {
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RuntimePropertyMixins.scala b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RuntimePropertyMixins.scala
index 2c5b7eb..c81316a 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RuntimePropertyMixins.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RuntimePropertyMixins.scala
@@ -256,8 +256,8 @@ trait ElementRuntimeValuedPropertiesMixin
   }
 
   final lazy val maybeByteOrderEv = {
-    if (isSimpleType && primType == PrimType.HexBinary) {
-      // xs:hexBinary types should ignore the byteOrder property and
+    if (isSimpleType && (primType == PrimType.HexBinary || primType == PrimType.AnyURI)) {
+      // xs:hexBinary and xs:anyURI types should ignore the byteOrder property and
       // effectively always use a bigEndian byteOrder. One way to accomplish
       // this would be to modify the byteOrderExpr to return a constant value
       // of "bigEndian" for hexBinary types. The problem with this is that
@@ -265,7 +265,7 @@ trait ElementRuntimeValuedPropertiesMixin
       // bitOrder="leastSignificantBitFirst", which is what would happen when
       // parsing hexBinary data with LSBF bitOrder, resulting in an SDE. So
       // instead, we just set this to Nope, and ensure we never try to get the
-      // byteOrder property when the type is xs:hexBinary. The IO layer will
+      // byteOrder property when the type is xs:hexBinary/anyURI. The IO layer will
       // see this Nope and do the right thing.
       Nope
     } else if (optionByteOrderRaw.isDefined) {
@@ -576,7 +576,7 @@ trait ElementRuntimeValuedPropertiesMixin
       }
 
     val byteOrderExprElts =
-      if (isSimpleType && primType =:= PrimType.HexBinary) {
+      if (isSimpleType && (primType =:= PrimType.HexBinary || primType =:= PrimType.AnyURI)) {
         ReferencedElementInfos.None
       } else {
         propExprElts(optionByteOrderRaw, byteOrderEv, f)
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SimpleTypes.scala b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SimpleTypes.scala
index ac0563a..8abbcb5 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SimpleTypes.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SimpleTypes.scala
@@ -170,6 +170,7 @@ object PrimitiveType {
       case PrimType.DateTime => DateTime
       case PrimType.Date => Date
       case PrimType.Time => Time
+      case PrimType.AnyURI => AnyURI
       case _ => Assert.usageError("Not a primitive type node: " + typeNode)
     }
   }
@@ -193,6 +194,7 @@ object PrimitiveType {
   val DateTime = new PrimitiveType(PrimType.DateTime)
   val Date = new PrimitiveType(PrimType.Date)
   val Time = new PrimitiveType(PrimType.Time)
+  val AnyURI = new PrimitiveType(PrimType.AnyURI)
 
 }
 
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ElementBaseGrammarMixin.scala b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ElementBaseGrammarMixin.scala
index 8064bba..2a4882c 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ElementBaseGrammarMixin.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ElementBaseGrammarMixin.scala
@@ -50,6 +50,7 @@ import org.apache.daffodil.grammar.primitives.BinaryFloat
 import org.apache.daffodil.grammar.primitives.BinaryIntegerKnownLength
 import org.apache.daffodil.grammar.primitives.BinaryIntegerPrefixedLength
 import org.apache.daffodil.grammar.primitives.BinaryIntegerRuntimeLength
+import org.apache.daffodil.grammar.primitives.BlobSpecifiedLength
 import org.apache.daffodil.grammar.primitives.CaptureContentLengthEnd
 import org.apache.daffodil.grammar.primitives.CaptureContentLengthStart
 import org.apache.daffodil.grammar.primitives.CaptureValueLengthEnd
@@ -692,6 +693,25 @@ trait ElementBaseGrammarMixin
     }
   }
 
+  private lazy val specifiedLengthBlob = prod("specifiedLengthBlob") {
+    lengthUnits match {
+      case LengthUnits.Bytes => BlobSpecifiedLength(this)
+      case LengthUnits.Bits => BlobSpecifiedLength(this)
+      case LengthUnits.Characters => SDE("lengthUnits='characters' is not valid for blob data.")
+    }
+  }
+
+  private lazy val blobValue = prod("blobValue") {
+    lengthKind match {
+      case LengthKind.Explicit => specifiedLengthBlob
+      case _ => SDE("objectKind='bytes' must have dfdl:lengthKind='explicit'")
+    }
+  }
+
+  private lazy val clobValue = prod("clobValue") {
+    notYetImplemented("objectKind='chars'")
+  }
+
   private lazy val textInt = prod("textInt", impliedRepresentation == Representation.Text) {
     standardTextInt || zonedTextInt
   }
@@ -1034,6 +1054,13 @@ trait ElementBaseGrammarMixin
       primType match {
         case PrimType.String => stringValue
         case PrimType.HexBinary => hexBinaryValue
+        case PrimType.AnyURI => {
+          val res = impliedRepresentation match {
+            case Representation.Binary => blobValue
+            case Representation.Text => clobValue
+          }
+          res
+        }
         case _ => {
           val res = impliedRepresentation match {
             case Representation.Binary => binaryValue
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/Padded.scala b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/Padded.scala
index 4b4e9c5..f859331 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/Padded.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/Padded.scala
@@ -119,6 +119,9 @@ trait PaddingInfoMixin {
       case PrimType.HexBinary => {
         (MaybeChar.Nope, TextJustificationType.None)
       }
+      case PrimType.AnyURI => {
+        (MaybeChar.Nope, TextJustificationType.None)
+      }
     }
     theJust
   }
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesLengthKind.scala b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesLengthKind.scala
index 95fa4ef..39ac0d7 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesLengthKind.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesLengthKind.scala
@@ -30,6 +30,7 @@ import org.apache.daffodil.processors.dfa.TextDelimitedParserWithEscapeBlock
 import org.apache.daffodil.processors.dfa.TextPaddingParser
 import org.apache.daffodil.processors.parsers.BCDDecimalDelimitedParser
 import org.apache.daffodil.processors.parsers.BCDIntegerDelimitedParser
+import org.apache.daffodil.processors.parsers.BlobSpecifiedLengthParser
 import org.apache.daffodil.processors.parsers.HexBinaryDelimitedParser
 import org.apache.daffodil.processors.parsers.HexBinaryEndOfBitLimitParser
 import org.apache.daffodil.processors.parsers.HexBinaryLengthPrefixedParser
@@ -44,6 +45,7 @@ import org.apache.daffodil.processors.parsers.StringOfSpecifiedLengthParser
 import org.apache.daffodil.processors.parsers.{ Parser => DaffodilParser }
 import org.apache.daffodil.processors.unparsers.BCDDecimalDelimitedUnparser
 import org.apache.daffodil.processors.unparsers.BCDIntegerDelimitedUnparser
+import org.apache.daffodil.processors.unparsers.BlobSpecifiedLengthUnparser
 import org.apache.daffodil.processors.unparsers.HexBinaryLengthPrefixedUnparser
 import org.apache.daffodil.processors.unparsers.HexBinaryMinLengthInBytesUnparser
 import org.apache.daffodil.processors.unparsers.HexBinarySpecifiedLengthUnparser
@@ -71,6 +73,13 @@ case class HexBinarySpecifiedLength(e: ElementBase) extends Terminal(e, true) {
   override lazy val unparser: DaffodilUnparser = new HexBinarySpecifiedLengthUnparser(e.elementRuntimeData, e.unparseTargetLengthInBitsEv)
 }
 
+case class BlobSpecifiedLength(e: ElementBase) extends Terminal(e, true) {
+
+  override lazy val parser = new BlobSpecifiedLengthParser(e.elementRuntimeData, e.elementLengthInBitsEv)
+
+  override lazy val unparser = new BlobSpecifiedLengthUnparser(e.elementRuntimeData, e.unparseTargetLengthInBitsEv)
+}
+
 case class StringOfSpecifiedLength(e: ElementBase) extends Terminal(e, true) with Padded {
 
   private def erd = e.elementRuntimeData
diff --git a/daffodil-io/src/main/scala/org/apache/daffodil/io/DataInputStream.scala b/daffodil-io/src/main/scala/org/apache/daffodil/io/DataInputStream.scala
index 5af3556..c2178ee 100644
--- a/daffodil-io/src/main/scala/org/apache/daffodil/io/DataInputStream.scala
+++ b/daffodil-io/src/main/scala/org/apache/daffodil/io/DataInputStream.scala
@@ -287,6 +287,17 @@ trait DataInputStream
   def getByteArray(bitLengthFrom1: Int, finfo: FormatInfo): Array[Byte]
 
   /**
+   * Same as getByteArray(Int, FormatInfo), except it accepts the array as a
+   * parameter and will fill that array with bytes. This can help to improve
+   * performance if an array can be reused to avoid allocations. But the caller
+   * must be careful upon return, since the array will not be cleared and may
+   * be bigger than the number of bits filled. It is the responsibility of the
+   * caller to know which bits in the array were set based on the bit length
+   * and format info.
+   */
+  def getByteArray(bitLengthFrom1: Int, finfo: FormatInfo, array: Array[Byte]): Unit
+
+  /**
    * Returns a long integer containing the bits between the current bit position
    * and that position plus the bitLength.
    * <p>
diff --git a/daffodil-io/src/main/scala/org/apache/daffodil/io/InputSourceDataInputStream.scala b/daffodil-io/src/main/scala/org/apache/daffodil/io/InputSourceDataInputStream.scala
index 124bb2c..064a821 100644
--- a/daffodil-io/src/main/scala/org/apache/daffodil/io/InputSourceDataInputStream.scala
+++ b/daffodil-io/src/main/scala/org/apache/daffodil/io/InputSourceDataInputStream.scala
@@ -154,6 +154,18 @@ final class InputSourceDataInputStream private (val inputSource: InputSource)
     array
   }
 
+  def getByteArray(bitLengthFrom1: Int, finfo: FormatInfo, array: Array[Byte]): Unit = {
+    // threadCheck()
+    if (!isDefinedForLength(bitLengthFrom1)) throw DataInputStream.NotEnoughDataException(bitLengthFrom1)
+
+    val bytesNeeded = (bitLengthFrom1 + 7) / 8
+    Assert.usage(array.size >= bytesNeeded)
+
+    fillByteArray(array, bitLengthFrom1, finfo)
+
+    setBitPos0b(bitPos0b + bitLengthFrom1)
+  }
+
   /**
    * Accepts a preallocated array and a bitLength. Reads the specified number
    * of bits and stores them in the array consistent with bitOrder and
diff --git a/daffodil-io/src/main/scala/org/apache/daffodil/io/StringDataInputStreamForUnparse.scala b/daffodil-io/src/main/scala/org/apache/daffodil/io/StringDataInputStreamForUnparse.scala
index 9033e58..eb96523 100644
--- a/daffodil-io/src/main/scala/org/apache/daffodil/io/StringDataInputStreamForUnparse.scala
+++ b/daffodil-io/src/main/scala/org/apache/daffodil/io/StringDataInputStreamForUnparse.scala
@@ -72,6 +72,7 @@ final class StringDataInputStreamForUnparse
   override def getUnsignedBigInt(bitLengthFrom1: Int, finfo: FormatInfo): BigInt = doNotUse
   override def getUnsignedLong(bitLengthFrom1To64: Int, finfo: FormatInfo): passera.unsigned.ULong = doNotUse
   override def getByteArray(bitLengthFrom1: Int, finfo: FormatInfo): Array[Byte] = doNotUse
+  override def getByteArray(bitLengthFrom1: Int, finfo: FormatInfo, array: Array[Byte]): Unit = doNotUse
   override def pastData(nBytesRequested: Int): java.nio.ByteBuffer = doNotUse
   override def setBitLimit0b(bitLimit0b: MaybeULong): Boolean = doNotUse
   override def setDebugging(setting: Boolean): Unit = doNotUse
diff --git a/daffodil-lib/src/main/scala/org/apache/daffodil/util/Misc.scala b/daffodil-lib/src/main/scala/org/apache/daffodil/util/Misc.scala
index 4632904..1eedccf 100644
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/util/Misc.scala
+++ b/daffodil-lib/src/main/scala/org/apache/daffodil/util/Misc.scala
@@ -17,20 +17,24 @@
 
 package org.apache.daffodil.util
 
-import java.io.InputStream
-import java.io.File
-import java.net.URL
-import java.net.URI
 import java.io.ByteArrayInputStream
-import java.nio.channels.ReadableByteChannel
-import java.nio.channels.WritableByteChannel
 import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.InputStream
+import java.net.URI
+import java.net.URL
 import java.net.URLClassLoader
 import java.nio.ByteBuffer
 import java.nio.CharBuffer
-import java.nio.charset.{ Charset => JavaCharset }
+import java.nio.channels.ReadableByteChannel
+import java.nio.channels.WritableByteChannel
 import java.nio.charset.CodingErrorAction
+import java.nio.charset.{ Charset => JavaCharset }
+import java.nio.file.Files
+import java.nio.file.Paths
+
 import scala.collection.JavaConverters._
+
 import org.apache.daffodil.equality._
 
 /**
@@ -121,6 +125,41 @@ object Misc {
     }
   }
 
+  /**
+   * Search for a resource name, trying a handful of heuristics.
+   *
+   * This is useful in cases where it's okay to be a little lax when searching
+   * for a resource name (e.g. testing). This is often needed in situations
+   * like directory structures not exactly match paths and we just want to try
+   * our best to find a file. Heuristics include things like ignoring leading
+   * directories in a resource, looking for a resource as a file, etc. This
+   * does mean that it's possible that this could find the wrong file if there
+   * are ambiguities, so be careful when using.
+   *
+   * If ambiguities would be a serious problem, use getResourceOption or
+   * getResourceRelativeOption.
+   */
+  def searchResourceOption(resName: String, relativeTo: Option[URI]): Option[URI] = {
+    val resAsURI = new URI(resName)
+    val resPath =
+      if (resAsURI.getScheme != null) Paths.get(resAsURI) else Paths.get(resName)
+    val resolvedURI =
+      if (Files.exists(resPath)) Some(resPath.toFile().toURI())
+      else Misc.getResourceRelativeOption(resName, relativeTo)
+    val res = resolvedURI.orElse {
+      // try ignoring the directory part
+      val parts = resName.split("/")
+      if (parts.length > 1) { // if there is one
+        val filePart = parts.last
+        val secondTry = searchResourceOption(filePart, relativeTo) // recursively
+        secondTry
+      } else {
+        None
+      }
+    }
+    res
+  }
+
   private def tryURL(url: URL): Option[URI] = {
     var is: InputStream = null
     val res =
diff --git a/daffodil-lib/src/main/scala/org/apache/daffodil/xml/XMLUtils.scala b/daffodil-lib/src/main/scala/org/apache/daffodil/xml/XMLUtils.scala
index 4fd7512..880ee66 100644
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/xml/XMLUtils.scala
+++ b/daffodil-lib/src/main/scala/org/apache/daffodil/xml/XMLUtils.scala
@@ -18,6 +18,10 @@
 package org.apache.daffodil.xml
 
 import java.io.File
+import java.net.URI
+import java.nio.file.Files
+import java.nio.file.Paths
+import java.nio.file.StandardOpenOption
 
 import scala.collection.mutable
 import scala.collection.mutable.ArrayBuilder
@@ -712,15 +716,20 @@ object XMLUtils {
               Assert.invariant(pre != "")
               false // drop dafint attributes.
             }
-            case xsiTypeAttr @ PrefixedAttribute(_, "type", _, _) if (NS(xsiTypeAttr.getNamespace(e)) == XMLUtils.XSI_NAMESPACE) =>
-              false // drop xsi:type attributes for now. Such time as we add
-            // support for them, we would need to not remove them.
-            // TODO: actually check xsi:type attributes are correct - but this
-            // requires schema-aware comparison.
-            case xsiTypeAttr @ PrefixedAttribute("xsi", "type", _, _) =>
-              false // drop xsi:type attributes for now. Even if prefix xsi is not defined.
-            // This just avoids having to edit many tests to add in the xmlns:xsi=....
-            // namespace declaration.
+            case xsiTypeAttr @ PrefixedAttribute(_, "type", _, _) if (NS(xsiTypeAttr.getNamespace(e)) == XMLUtils.XSI_NAMESPACE) => {
+              // TODO: actually check xsi:type attributes are correct according
+              // to the schema--requires schema-awareness in TDML Runner.
+              // Do not hide xsi:type since it is used for hints for type aware
+              // comparisons.
+              true
+            }
+            case xsiTypeAttr @ PrefixedAttribute("xsi", "type", _, _) => {
+              // TODO: actually check xsi:type attributes are correct according
+              // to the schema--requires schema-awareness in TDML Runner.
+              // Do not hide xsi:type since it is used for hints for type aware
+              // comparisons.
+              true
+            }
             case attr =>
               true // keep all other attributes
           }
@@ -898,13 +907,68 @@ Differences were (path, expected, actual):
     computeTextDiff(zPath, dataA, dataB, maybeType)
   }
 
+  def computeBlobDiff(zPath: String, dataA: String, dataB: String) = {
+    val uriA = Misc.searchResourceOption(dataA, None)
+    val uriB = Misc.searchResourceOption(dataB, None)
+
+    val pathA = uriA.map { u => Paths.get(u) }
+    val pathB = uriB.map { u => Paths.get(u) }
+
+    val canReadA = pathA.map { p => Files.isReadable(p) }.getOrElse(false)
+    val canReadB = pathB.map { p => Files.isReadable(p) }.getOrElse(false)
+
+    if (!canReadA || !canReadB) {
+      val path = zPath + ".canRead"
+      Seq((path, canReadA.toString, canReadB.toString))
+    } else {
+      val CHUNK_SIZE = 1024
+      val arrayA = new Array[Byte](CHUNK_SIZE)
+      val arrayB = new Array[Byte](CHUNK_SIZE)
+
+      val streamA = Files.newInputStream(pathA.get, StandardOpenOption.READ)
+      val streamB = Files.newInputStream(pathB.get, StandardOpenOption.READ)
+
+      var lenA: Int = 0
+      var lenB: Int = 0
+      var numSameBytes: Int = 0
+      var areSame: Boolean = true
+
+      while ({
+          lenA = streamA.read(arrayA)
+          lenB = streamB.read(arrayB)
+          areSame = lenA == lenB && arrayA.sameElements(arrayB)
+          areSame && lenA != -1 && lenB != -1
+      }) {
+        numSameBytes += lenA
+      }
+
+      if (!areSame) {
+        val zip = arrayA.zip(arrayB)
+        val firstDiffIndex = zip.indexWhere(z => z._1 != z._2)
+
+        val MAX_CONTEXT = 40
+        val contextA = arrayA.take(lenA).drop(firstDiffIndex).take(MAX_CONTEXT)
+        val contextB = arrayB.take(lenB).drop(firstDiffIndex).take(MAX_CONTEXT)
+        val hexA = Misc.bytes2Hex(contextA)
+        val hexB = Misc.bytes2Hex(contextB)
+
+        val absoluteIndex = numSameBytes + firstDiffIndex
+        val path = zPath + ".bytesAt(" + (absoluteIndex + 1) + ")"
+        Seq((path, hexA, hexB))
+      } else {
+        Nil
+      }
+    }
+  }
+
   def computeTextDiff(
     zPath: String,
     dataA: String,
     dataB: String,
     maybeType: Option[String]): Seq[(String, String, String)] = {
 
-    if (textIsSame(dataA, dataB, maybeType)) Nil
+    if (maybeType.isDefined && maybeType.get == "xs:anyURI") computeBlobDiff(zPath, dataA, dataB)
+    else if (textIsSame(dataA, dataB, maybeType)) Nil
     else {
       // There must be some difference, so let's find just the first index of
       // difference and we'll include that and some following characters for
diff --git a/daffodil-lib/src/test/scala/org/apache/daffodil/xml/test/unit/TestXMLUtils.scala b/daffodil-lib/src/test/scala/org/apache/daffodil/xml/test/unit/TestXMLUtils.scala
index 4bf17dc..ed2486a 100644
--- a/daffodil-lib/src/test/scala/org/apache/daffodil/xml/test/unit/TestXMLUtils.scala
+++ b/daffodil-lib/src/test/scala/org/apache/daffodil/xml/test/unit/TestXMLUtils.scala
@@ -17,13 +17,21 @@
 
 package org.apache.daffodil.xml.test.unit
 
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.nio.file.StandardOpenOption
+
 import scala.xml._
-import org.apache.daffodil.xml.XMLUtils
-import org.apache.daffodil.xml.JDOMUtils
-import org.apache.daffodil.xml.NS
+
 import junit.framework.Assert._
 import org.junit.Test
+
 import org.apache.daffodil.Implicits._
+import org.apache.daffodil.util.Misc
+import org.apache.daffodil.xml.JDOMUtils
+import org.apache.daffodil.xml.NS
+import org.apache.daffodil.xml.XMLUtils
 
 class TestXMLUtils {
 
@@ -261,4 +269,68 @@ class TestXMLUtils {
     val expected = "&#x80;&#x81;&#x82;&#x83;&#x84;&#x85;&#x86;&#x87;&#x88;&#x89;&#x8A;&#x8B;&#x8C;&#x8D;&#x8E;&#x8F;&#x90;&#x91;&#x92;&#x93;&#x94;&#x95;&#x96;&#x97;&#x98;&#x99;&#x9A;&#x9B;&#x9C;&#x9D;&#x9E;&#x9F;&#xA0;¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
     assertEquals(expected, actual)
   }
+
+  private def createBlobFile(blob: String): Path = {
+    val dir = Paths.get(System.getProperty("java.io.tmpdir"), "daffodil-test", "blobs")
+    Files.createDirectories(dir)
+    val path = Files.createTempFile(dir, null, null)
+    val bytes = Misc.hex2Bytes(blob)
+    val stream = Files.newOutputStream(path, StandardOpenOption.WRITE)
+    stream.write(bytes)
+    stream.close()
+    path
+  }
+
+  @Test def testBlobDiff01() {
+    val path1 = createBlobFile("A1B2C3")
+    val path2 = createBlobFile("A1B2C3")
+    val diff = XMLUtils.computeBlobDiff("path", path1.toString, path2.toString)
+    assertEquals(diff, Nil)
+    Files.delete(path1)
+    Files.delete(path2)
+  }
+
+  @Test def testBlobDiff02() {
+    val path1 = createBlobFile("A1B2C3D4")
+    val path2 = createBlobFile("A1B1C3")
+    val diff = XMLUtils.computeBlobDiff("path", path1.toString, path2.toString)
+    assertEquals("path.bytesAt(2)", diff(0)._1)
+    assertEquals("B2C3D4", diff(0)._2)
+    assertEquals("B1C3", diff(0)._3)
+    Files.delete(path1)
+    Files.delete(path2)
+  }
+
+  @Test def testBlobDiff03() {
+    val path1 = createBlobFile("A1B2C3D4")
+    val path2 = createBlobFile("")
+    val diff = XMLUtils.computeBlobDiff("path", path1.toString, path2.toString)
+    assertEquals("path.bytesAt(1)", diff(0)._1)
+    assertEquals("A1B2C3D4", diff(0)._2)
+    assertEquals("", diff(0)._3)
+    Files.delete(path1)
+    Files.delete(path2)
+  }
+
+  @Test def testBlobDiff04() {
+    val path1 = createBlobFile("A1B2C3D4")
+    val path2 = Paths.get("file://does/not/exist")
+    val diff = XMLUtils.computeBlobDiff("path", path1.toString, path2.toString)
+    assertEquals("path.canRead", diff(0)._1)
+    assertEquals("true", diff(0)._2)
+    assertEquals("false", diff(0)._3)
+    Files.delete(path1)
+  }
+
+  @Test def testBlobDiff05() {
+    val path1 = createBlobFile(("00" * 1024) + ("A1" * 41))
+    val path2 = createBlobFile(("00" * 1024))
+    val diff = XMLUtils.computeBlobDiff("path", path1.toString, path2.toString)
+    println(diff)
+    assertEquals("path.bytesAt(1025)", diff(0)._1)
+    assertEquals("A1" * 40, diff(0)._2)
+    assertEquals("", diff(0)._3)
+    Files.delete(path1)
+    Files.delete(path2)
+  }
 }
diff --git a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/DFDL_part2_attributes.xsd b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/DFDL_part2_attributes.xsd
index cebc507..4b9780d 100644
--- a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/DFDL_part2_attributes.xsd
+++ b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/DFDL_part2_attributes.xsd
@@ -1066,6 +1066,7 @@
     <xsd:attributeGroup ref="dfdl:BooleanTextAGQualified" />
     <xsd:attributeGroup ref="dfdl:BooleanBinaryAGQualified" />
     <xsd:attributeGroup ref="dfdlx:SimpleTypeValueCalcAGQualified" />
+    <xsd:attributeGroup ref="dfdlx:ObjectAGQualified" />
   </xsd:attributeGroup>
 
   <xsd:attributeGroup name="LayeringAGQualified">
diff --git a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/DFDL_part3_model.xsd b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/DFDL_part3_model.xsd
index 524087e..101b26c 100644
--- a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/DFDL_part3_model.xsd
+++ b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/DFDL_part3_model.xsd
@@ -365,6 +365,7 @@
     <xsd:attributeGroup ref="dfdl:BooleanTextAG" />
     <xsd:attributeGroup ref="dfdl:BooleanBinaryAG" />
     <xsd:attributeGroup ref="dfdlx:SimpleTypeValueCalcAG" />
+    <xsd:attributeGroup ref="dfdlx:ObjectAG" />
   </xsd:attributeGroup>
 
 </xsd:schema>
diff --git a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
index 9b7884d..6923c7c 100644
--- a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
+++ b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
@@ -112,6 +112,13 @@
             </xs:documentation>
           </xs:annotation>
         </xs:element>
+        <xs:element name="blobChunkSizeInBytes" type="xs:int" default="4096" minOccurs="0">
+          <xs:annotation>
+            <xs:documentation>
+              When reading/writing blob data, the maximum number of bytes to read/write at a time.
+            </xs:documentation>
+          </xs:annotation>
+        </xs:element>
         <xs:element name="defaultInitialRegexMatchLimitInChars" type="xs:int" default="32" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
diff --git a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dfdlx.xsd b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dfdlx.xsd
index 7bee32b..2baac58 100644
--- a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dfdlx.xsd
+++ b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dfdlx.xsd
@@ -39,6 +39,7 @@
       <xs:enumeration value="dfdlx:choiceDispatchKeyKind" />
       <xs:enumeration value="dfdlx:emptyElementParsePolicy"/>
       <xs:enumeration value="dfdlx:inputTypeCalc"/>
+      <xs:enumeration value="dfdlx:objectKind"/>
       <xs:enumeration value="dfdlx:outputTypeCalc"/>
       <xs:enumeration value="dfdlx:parseUnparsePolicy"/>
       <xs:enumeration value="dfdlx:repType"/>
@@ -119,4 +120,19 @@
     <xs:attribute form="qualified" name="layerBoundaryMark" type="dfdl:ListOfDFDLStringLiteral_Or_DFDLExpression" />
   </xs:attributeGroup>
 
+  <xs:attributeGroup name="ObjectAG">
+    <xs:attribute form="qualified" name="ObjectKind" type="dfdlx:ObjectKindType" />
+  </xs:attributeGroup>
+
+  <xs:attributeGroup name="ObjectAGQualified">
+    <xs:attribute form="qualified" name="objectKind" type="dfdlx:ObjectKindType" />
+  </xs:attributeGroup>
+
+  <xs:simpleType name="ObjectKindType">
+    <xs:restriction base="xs:string">
+      <xs:enumeration value="bytes"/>
+      <xs:enumeration value="chars"/>
+    </xs:restriction>
+  </xs:simpleType>
+
 </xs:schema>
diff --git a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/BlobLengthUnparser.scala b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/BlobLengthUnparser.scala
new file mode 100644
index 0000000..b991e6f
--- /dev/null
+++ b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/BlobLengthUnparser.scala
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.daffodil.processors.unparsers
+
+import passera.unsigned.ULong
+
+import java.net.URI
+import java.nio.file.Files
+import java.nio.file.Paths
+import java.nio.file.StandardOpenOption
+
+import org.apache.daffodil.infoset.RetryableException
+import org.apache.daffodil.processors.ElementRuntimeData
+import org.apache.daffodil.processors.UnparseTargetLengthInBitsEv
+import org.apache.daffodil.util.Maybe._
+
+abstract class BlobUnparserBase(override val context: ElementRuntimeData)
+  extends PrimUnparser {
+
+  override lazy val runtimeDependencies = Vector()
+
+  protected def getLengthInBits(state: UState): Long
+
+  override def unparse(state: UState): Unit = {
+
+    val node = state.currentInfosetNode.asSimple
+    val value = node.dataValue.asInstanceOf[URI]
+    val lengthInBits = getLengthInBits(state)
+
+    val lengthInBytes = (lengthInBits + 7) / 8
+
+    val uriScheme = value.getScheme
+    val uriPath = value.getPath
+
+    if (uriScheme != "file") {
+      UnparseError(
+        One(context.schemaFileLocation),
+        One(state.currentLocation),
+        "Blob URI must be a file: %s", value.toString)
+    }
+
+    val path = Paths.get(value)
+
+    val fileSizeInBytes = try {
+      Files.size(path)
+    } catch {
+      case e: Exception => 
+        UnparseError(
+          One(context.schemaFileLocation),
+          One(state.currentLocation),
+          "Unable to open blob for reading: %s",
+          value.toString)
+    }
+
+    if (fileSizeInBytes > lengthInBytes) {
+      UnparseError(
+        One(context.schemaFileLocation),
+        One(state.currentLocation),
+        "Blob length (%d bits) exceeds explicit length value: %d bits",
+        fileSizeInBytes * 8,
+        lengthInBits)
+    }
+
+    val blobStream =
+      try {
+        Files.newInputStream(path, StandardOpenOption.READ)
+      } catch {
+        case e: Exception =>
+          UnparseError(
+            One(context.schemaFileLocation),
+            One(state.currentLocation),
+            "Unable to open blob for reading: %s",
+            value.toString)
+      }
+
+    val dos = state.dataOutputStream
+
+    val array = new Array[Byte](state.tunable.blobChunkSizeInBytes)
+
+    var remainingBitsToPut = lengthInBits
+    var fileHasData = true
+    while (remainingBitsToPut > 0 && fileHasData) {
+      val bitsToRead = Math.min(remainingBitsToPut, state.tunable.blobChunkSizeInBytes * 8)
+      val bytesToRead = (bitsToRead + 7) / 8
+      val bytesRead = blobStream.read(array, 0, bytesToRead.toInt)
+      if (bytesRead == -1) {
+        fileHasData = false
+      } else {
+        val bitsToPut = Math.min(bytesRead * 8, bitsToRead)
+        val ret = dos.putByteArray(array, bitsToPut.toInt, state)
+        if (!ret) {
+          blobStream.close()
+          UnparseError(
+            One(context.schemaFileLocation),
+            One(state.currentLocation),
+            "Failed to write %d blob bits",
+            lengthInBits)
+        }
+        remainingBitsToPut -= bitsToPut
+      }
+    }
+
+    blobStream.close()
+
+    // calculate the skip bits
+    val nFillBits = remainingBitsToPut
+    if (nFillBits > 0) {
+      val ret = dos.skip(nFillBits, state)
+      if (!ret) {
+        UnparseError(Nope, One(state.currentLocation), "Failed to skip %d bits.", nFillBits)
+      }
+    }
+  }
+}
+
+final class BlobSpecifiedLengthUnparser(erd: ElementRuntimeData, val lengthEv: UnparseTargetLengthInBitsEv)
+  extends BlobUnparserBase(erd) {
+
+  override def getLengthInBits(state: UState): Long = {
+    val l: Long = try {
+      lengthEv.evaluate(state).getULong.toLong
+    } catch {
+      case e: RetryableException => {
+        val uri = state.currentInfosetNode.asSimple.dataValue.asInstanceOf[URI]
+        val path = Paths.get(uri)
+        val len = Files.size(path) * 8
+        len
+      }
+    }
+    l
+  }
+}
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/NodeInfo.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/NodeInfo.scala
index 0dc69a9..6246935 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/NodeInfo.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/NodeInfo.scala
@@ -17,28 +17,29 @@
 
 package org.apache.daffodil.dpath
 
+import java.lang.{ Boolean => JBoolean }
+import java.lang.{ Byte => JByte }
+import java.lang.{ Double => JDouble }
+import java.lang.{ Float => JFloat }
+import java.lang.{ Integer => JInt }
+import java.lang.{ Long => JLong }
+import java.lang.{ Short => JShort }
+import java.math.{ BigDecimal => JBigDecimal }
+import java.math.{ BigInteger => JBigInt }
+import java.net.URI
+
 import scala.BigDecimal
 import scala.BigInt
+
 import org.apache.daffodil.calendar._
 import org.apache.daffodil.exceptions.Assert
 import org.apache.daffodil.util.Enum
 import org.apache.daffodil.util.Misc
 import org.apache.daffodil.xml.GlobalQName
+import org.apache.daffodil.xml.NoNamespace
 import org.apache.daffodil.xml.QName
 import org.apache.daffodil.xml.RefQName
 import org.apache.daffodil.xml.XMLUtils
-import java.lang.{
-  Long => JLong,
-  Double => JDouble,
-  Float => JFloat,
-  Integer => JInt,
-  Short => JShort,
-  Byte => JByte,
-  Boolean => JBoolean
-}
-import org.apache.daffodil.exceptions.Assert
-import java.math.{ BigDecimal => JBigDecimal, BigInteger => JBigInt }
-import org.apache.daffodil.xml.NoNamespace
 
 /**
  * We need to have a data structure that lets us represent a type, and
@@ -202,6 +203,7 @@ object NodeInfo extends Enum {
       case x: Double => NodeInfo.Double
       case x: Float => NodeInfo.Float
       case x: Array[Byte] => NodeInfo.HexBinary
+      case x: URI => NodeInfo.AnyURI
       case x: Boolean => NodeInfo.Boolean
       case x: DFDLCalendar => NodeInfo.DateTime
       case _ => Assert.usageError("Unsupported object representation type: %s".format(a))
@@ -241,9 +243,22 @@ object NodeInfo extends Enum {
         Date, Time, DateTime,
         UnsignedByte, Byte,
         HexBinary,
+        AnyURI,
         String, NonEmptyString), Nil)
-    with Boolean.Kind with Complex.Kind with Array.Kind
-    with ArrayIndex.Kind with Double.Kind with Float.Kind with Date.Kind with Time.Kind with DateTime.Kind with UnsignedByte.Kind with Byte.Kind with HexBinary.Kind with NonEmptyString.Kind
+    with Boolean.Kind
+    with Complex.Kind
+    with Array.Kind
+    with ArrayIndex.Kind
+    with Double.Kind
+    with Float.Kind
+    with Date.Kind
+    with Time.Kind
+    with DateTime.Kind
+    with UnsignedByte.Kind
+    with Byte.Kind
+    with HexBinary.Kind
+    with NonEmptyString.Kind
+    with AnyURI.Kind
 
   /**
    * All complex types are represented by this one type object.
@@ -277,7 +292,7 @@ object NodeInfo extends Enum {
   }
 
   protected sealed trait AnyAtomicKind extends AnySimpleType.Kind
-  case object AnyAtomic extends TypeNode(AnySimpleType, Seq(String, Numeric, Boolean, Opaque, AnyDateTime)) with AnyAtomicKind {
+  case object AnyAtomic extends TypeNode(AnySimpleType, Seq(String, Numeric, Boolean, Opaque, AnyDateTime, AnyURI)) with AnyAtomicKind {
     type Kind = AnyAtomicKind
   }
 
@@ -338,6 +353,7 @@ object NodeInfo extends Enum {
   val Double = PrimType.Double
   val Float = PrimType.Float
   val HexBinary = PrimType.HexBinary
+  val AnyURI = PrimType.AnyURI
   val Boolean = PrimType.Boolean
   val DateTime = PrimType.DateTime
   val Date = PrimType.Date
@@ -359,6 +375,7 @@ object NodeInfo extends Enum {
     Double,
     Float,
     HexBinary,
+    AnyURI,
     Boolean,
     DateTime,
     Date,
@@ -494,6 +511,12 @@ object NodeInfo extends Enum {
       override def fromXMLString(s: String): JBoolean = s.toBoolean
     }
 
+    protected sealed trait AnyURIKind extends AnySimpleType.Kind
+    case object AnyURI extends PrimTypeNode(AnyAtomic) with AnyURIKind {
+      type Kind = AnyURIKind
+      override def fromXMLString(s: String): URI = new URI(s)
+    }
+
     protected sealed trait HexBinaryKind extends Opaque.Kind
     case object HexBinary extends PrimTypeNode(Opaque) with HexBinaryKind {
       type Kind = HexBinaryKind
@@ -538,7 +561,7 @@ object NodeInfo extends Enum {
   private lazy val allDFDLTypes = List(
     Float, Double, Decimal, Integer, Long, Int, Short, Byte,
     NonNegativeInteger, UnsignedLong, UnsignedInt, UnsignedShort, UnsignedByte,
-    String, Boolean, HexBinary,
+    String, Boolean, HexBinary, AnyURI,
     Date, Time, DateTime)
 
   lazy val allTypes =
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/InfosetImpl.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/InfosetImpl.scala
index ae0a971..4b2b6e1 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/InfosetImpl.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/InfosetImpl.scala
@@ -1146,6 +1146,7 @@ sealed class DISimple(override val erd: ElementRuntimeData)
           case b: JBoolean => b
           case _: AtomicLong | _: AtomicInteger => Assert.invariantFailed("Unsupported type. %s of type %s.".format(x, Misc.getNameFromClass(x)))
           case n: java.lang.Number => n
+          case u: java.net.URI => u
           case ar: AnyRef => Assert.invariantFailed("Unsupported type. %s of type %s.".format(x, Misc.getNameFromClass(x)))
         }
       }
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/InfosetOutputter.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/InfosetOutputter.scala
index 5c0c195..8f12913 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/InfosetOutputter.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/infoset/InfosetOutputter.scala
@@ -17,6 +17,8 @@
 
 package org.apache.daffodil.infoset
 
+import java.nio.file.Path
+import java.nio.file.Paths
 
 trait InfosetOutputter {
 
@@ -132,6 +134,38 @@ trait InfosetOutputter {
     val maybeIsNilled = diElement.maybeIsNilled
     maybeIsNilled.isDefined && maybeIsNilled.get == true
   }
+
+  /**
+   * Set the attributes for how to create blob files.
+   *
+   * @param dir the Path the the directory to create files. If the directory
+   *            does not exist, Daffodil will attempt to create it before
+   *            writing a blob.
+   * @param prefix the prefix string to be used in generating a blob file name
+   * @param suffix the suffix string to be used in generating a blob file name
+   */
+  final def setBlobAttributes(dir: Path, prefix: String, suffix: String): Unit = {
+    blobDirectory = dir
+    blobPrefix = prefix
+    blobSuffix = suffix
+  }
+
+  /**
+   * Get the list of blob paths that were output in the infoset.
+   *
+   * This is the same as what would be found by iterating over the infoset.
+   */
+  final def getBlobPaths(): Seq[Path] = blobPaths
+
+
+  final def getBlobDirectory(): Path = blobDirectory
+  final def getBlobPrefix(): String = blobPrefix
+  final def getBlobSuffix(): String = blobSuffix
+  final def setBlobPaths(paths: Seq[Path]): Unit = blobPaths = paths
+  private var blobDirectory: Path = Paths.get(System.getProperty("java.io.tmpdir"))
+  private var blobPrefix: String = "daffodil-"
+  private var blobSuffix: String = ".blob"
+  private var blobPaths: Seq[Path] = Seq.empty
 }
 
 object Status extends Enumeration {
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala
index e318c95..494f55e 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala
@@ -21,6 +21,7 @@ import java.io.File
 import java.io.ObjectOutputStream
 import java.nio.channels.Channels
 import java.nio.CharBuffer
+import java.nio.file.Files
 import java.nio.LongBuffer
 import java.util.zip.GZIPOutputStream
 
@@ -205,6 +206,14 @@ class DataProcessor(val ssrd: SchemaSetRuntimeData)
         // so that we can start to throw away infoset nodes. When that happens
         // we can remove this line.
         state.infoset.visit(state.output)
+        state.output.setBlobPaths(state.blobPaths)
+      } else {
+        // failed, so delete all blobs that were created
+        state.blobPaths.foreach { path =>
+          Files.delete(path)
+        }
+        // ensure the blob paths are empty in case of outputter reuse
+        state.output.setBlobPaths(Seq.empty)
       }
       val s = state
       val dp = s.dataProc
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/BlobLengthParsers.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/BlobLengthParsers.scala
new file mode 100644
index 0000000..6de45a7
--- /dev/null
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/BlobLengthParsers.scala
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.daffodil.processors.parsers
+
+import java.nio.file.Files
+import java.nio.file.StandardOpenOption
+
+import org.apache.daffodil.processors.ElementRuntimeData
+import org.apache.daffodil.processors.LengthInBitsEv
+import org.apache.daffodil.util.MaybeULong
+
+sealed abstract class BlobLengthParser(override val context: ElementRuntimeData)
+  extends PrimParser {
+
+  protected def getLengthInBits(pstate: PState): Long
+
+  override final def parse(start: PState): Unit = {
+    val dis = start.dataInputStream
+    val currentElement = start.simpleElement
+    val nBits = getLengthInBits(start)
+
+    val blobPath = try {
+      val blobDir = start.output.getBlobDirectory
+      Files.createDirectories(blobDir)
+      Files.createTempFile(blobDir, start.output.getBlobPrefix, start.output.getBlobSuffix)
+    } catch {
+      case e: Exception => start.SDE("Unable to create blob file: ", e.getMessage)
+    }
+    val blobStream = Files.newOutputStream(blobPath, StandardOpenOption.WRITE)
+
+    var remainingBitsToGet = nBits
+
+    val array = new Array[Byte](start.tunable.blobChunkSizeInBytes)
+    val blobChunkSizeInBits = start.tunable.blobChunkSizeInBytes * 8
+
+    while (remainingBitsToGet > 0) {
+      val bitsToGet = Math.min(remainingBitsToGet, blobChunkSizeInBits).toInt
+      if (dis.isDefinedForLength(bitsToGet)) {
+        start.dataInputStream.getByteArray(bitsToGet, start, array)
+        val bytesToPut = (bitsToGet + 7) / 8
+        blobStream.write(array, 0, bytesToPut)
+        remainingBitsToGet -= bitsToGet
+      } else {
+        val remainingBits =
+          if (dis.remainingBits.isDefined) {
+            val totalBitsRead = nBits - remainingBitsToGet
+            MaybeULong(dis.remainingBits.get + totalBitsRead)
+          } else {
+            MaybeULong.Nope
+          }
+        PENotEnoughBits(start, nBits, remainingBits)
+        remainingBitsToGet = 0 // break out of the loop
+      }
+    }
+
+    blobStream.close()
+
+    if (start.isSuccess) {
+      currentElement.setDataValue(blobPath.toUri)
+      start.addBlobPath(blobPath)
+    } else {
+      Files.delete(blobPath)
+    }
+
+  }
+}
+
+final class BlobSpecifiedLengthParser(erd: ElementRuntimeData, lengthEv: LengthInBitsEv)
+  extends BlobLengthParser(erd) {
+
+  override val runtimeDependencies = Vector(lengthEv)
+
+  override def getLengthInBits(pstate: PState): Long = {
+    lengthEv.evaluate(pstate).get
+  }
+
+}
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/PState.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/PState.scala
index 23b1ef2..dda6caa 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/PState.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/PState.scala
@@ -18,6 +18,8 @@
 package org.apache.daffodil.processors.parsers
 
 import java.nio.channels.Channels
+import java.nio.file.Files
+import java.nio.file.Path
 
 import scala.Right
 import scala.collection.mutable
@@ -153,6 +155,7 @@ final class PState private (
   val mpstate: MPState,
   dataProcArg: DataProcessor,
   var delimitedParseResult: Maybe[dfa.ParseResult],
+  var blobPaths: Seq[Path],
   tunable: DaffodilTunables) // Runtime tunables obtained from DataProcessor)
   extends ParseOrUnparseState(vmap, diagnosticsArg, One(dataProcArg), tunable) {
 
@@ -276,6 +279,10 @@ final class PState private (
     discriminatorStack.push(disc)
   }
 
+  def addBlobPath(path: Path) {
+    blobPaths = path +: blobPaths
+  }
+
   final def notifyDebugging(flag: Boolean) {
     // threadCheck()
     dataInputStream.setDebugging(flag)
@@ -392,6 +399,7 @@ object PState {
     var validationStatus: Boolean = _
     var diagnostics: List[Diagnostic] = _
     var delimitedParseResult: Maybe[dfa.ParseResult] = Nope
+    var blobPaths: Seq[Path] = Seq.empty
 
     val mpStateMark = new MPState.Mark
 
@@ -405,6 +413,7 @@ object PState {
       diagnostics = null
       delimitedParseResult = Nope
       mpStateMark.clear()
+      blobPaths = Seq.empty
       // DO NOT clear requestorId. It is there to help us debug if we try to repeatedly reset/discard a mark already discarded.
     }
 
@@ -420,6 +429,7 @@ object PState {
       this.validationStatus = ps.validationStatus
       this.diagnostics = ps.diagnostics
       this.mpStateMark.captureFrom(ps.mpstate)
+      this.blobPaths = ps.blobPaths
     }
 
     def restoreInfoset(ps: PState) = {
@@ -439,6 +449,20 @@ object PState {
       ps.diagnostics = this.diagnostics
       ps.delimitedParseResult = this.delimitedParseResult
       mpStateMark.restoreInto(ps.mpstate)
+
+      // We are backtracking here, potentially past blob files that have
+      // already been written, so we must delete them. Since we always prepend
+      // blobs to the blobPaths Seq as they are created, we can delete them by
+      // deleting all blob files that are in front of the blobsToKeep Seq. This
+      // also lets us do fast reference equality comparisons for determining
+      // when to stop deleting.
+      val blobsToKeep = this.blobPaths
+      var currentBlobs = ps.blobPaths
+      while (currentBlobs ne blobsToKeep) {
+          Files.delete(currentBlobs.head)
+          currentBlobs = currentBlobs.tail
+      }
+      ps.blobPaths = blobsToKeep
     }
 
   }
@@ -478,7 +502,7 @@ object PState {
 
     dis.cst.setPriorBitOrder(root.defaultBitOrder)
     val newState = new PState(doc.asInstanceOf[DIElement], dis, output, variables, diagnostics, mutablePState,
-      dataProc.asInstanceOf[DataProcessor], Nope, tunables)
+      dataProc.asInstanceOf[DataProcessor], Nope, Seq.empty, tunables)
     newState
   }
 
diff --git a/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala b/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala
index e1bccc1..43c6748 100644
--- a/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala
+++ b/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala
@@ -409,22 +409,7 @@ class DFDLTestSuite private[tdml] (
    * directory as the tdml file, and some other variations.
    */
   def findTDMLResource(resName: String): Option[URI] = {
-    val resPath = Paths.get(resName)
-    val resolvedURI =
-      if (Files.exists(resPath)) Some(resPath.toFile().toURI())
-      else Misc.getResourceRelativeOption(resName, Some(tsURI))
-    val res = resolvedURI.orElse {
-      // try ignoring the directory part
-      val parts = resName.split("/")
-      if (parts.length > 1) { // if there is one
-        val filePart = parts.last
-        val secondTry = findTDMLResource(filePart) // recursively
-        secondTry
-      } else {
-        None
-      }
-    }
-    res
+    Misc.searchResourceOption(resName, Some(tsURI))
   }
 
   def findEmbeddedSchema(modelName: String): Option[DefinedSchema] = {
@@ -869,6 +854,12 @@ case class ParserTestCase(ptc: NodeSeq, parentArg: DFDLTestSuite)
             case t: Throwable => toss(t, implString)
           }
 
+        // we should never need blobs if we're expecting an error even if we
+        // don't get errors. So clean them up immediately
+        actual.getBlobPaths.foreach { blobPath =>
+          Files.delete(blobPath)
+        }
+
         val isErr: Boolean =
           if (actual.isProcessingError) true
           else {
@@ -1109,6 +1100,13 @@ case class ParserTestCase(ptc: NodeSeq, parentArg: DFDLTestSuite)
     roundTrip match {
       case NoRoundTrip => {
         // done. Do nothing else.
+
+        // Done with the first parse result, safe to clean up blobs if there
+        // was success. This won't get called on failure, which is fine--leave
+        // blobs around for debugging
+        firstParseResult.getBlobPaths.foreach { blobPath =>
+          Files.delete(blobPath)
+        }
       }
       case OnePassRoundTrip => {
         val outStream = new java.io.ByteArrayOutputStream()
@@ -1119,6 +1117,13 @@ case class ParserTestCase(ptc: NodeSeq, parentArg: DFDLTestSuite)
         // directly back to the original input form.
 
         VerifyTestCase.verifyUnparserTestData(new ByteArrayInputStream(firstParseTestData), outStream, implString)
+
+        // Done with the first parse result, safe to clean up blobs if there
+        // was success. This won't get called on failure, which is fine--leave
+        // blobs around for debugging
+        firstParseResult.getBlobPaths.foreach { blobPath =>
+          Files.delete(blobPath)
+        }
       }
       case TwoPassRoundTrip => {
         //
@@ -1140,6 +1145,16 @@ case class ParserTestCase(ptc: NodeSeq, parentArg: DFDLTestSuite)
         verifyParseResults(processor, actual, testInfoset, implString)
         verifyLeftOverData(actual, reParseTestDataLength, implString)
         // if it doesn't pass, it will throw out of here.
+
+        // Done with the first and second parse resultrs, safe to clean up
+        // blobs if there was success. This won't get called on failure, which
+        // is fine--leave blobs around for debugging
+        firstParseResult.getBlobPaths.foreach { blobPath =>
+          Files.delete(blobPath)
+        }
+        actual.getBlobPaths.foreach { blobPath =>
+          Files.delete(blobPath)
+        }
       }
       case ThreePassRoundTrip => {
         //
@@ -1180,8 +1195,19 @@ case class ParserTestCase(ptc: NodeSeq, parentArg: DFDLTestSuite)
         val thirdPassOutStream = new java.io.ByteArrayOutputStream()
         doOnePassRoundTripUnparseExpectSuccess(processor, thirdPassOutStream, firstParseResult, implString)
         VerifyTestCase.verifyUnparserTestData(new ByteArrayInputStream(reParseTestData), thirdPassOutStream, implString)
+
+        // Done with the first parse result and second parse results. Safe to
+        // clean up blobs if there was success. Leave them around for debugging
+        // if there was a failure
+        firstParseResult.getBlobPaths.foreach { blobPath =>
+          Files.delete(blobPath)
+        }
+        secondParseResult.getBlobPaths.foreach { blobPath =>
+          Files.delete(blobPath)
+        }
       }
     }
+
   }
 }
 
@@ -1325,6 +1351,14 @@ case class UnparserTestCase(ptc: NodeSeq, parentArg: DFDLTestSuite)
       leftOverException.map {
         throw _
       } // if we get here, throw the left over data exception.
+
+      // Done with the parse results, safe to clean up blobs if there was
+      // success. This won't get called on failure, which is fine--leave blobs
+      // around for debugging
+      parseActual.getBlobPaths.foreach { blobPath =>
+        Files.delete(blobPath)
+      }
+
     }
   }
 
diff --git a/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/processor/TDMLDFDLProcessor.scala b/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/processor/TDMLDFDLProcessor.scala
index 1a0c2cd..3b5a908 100644
--- a/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/processor/TDMLDFDLProcessor.scala
+++ b/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/processor/TDMLDFDLProcessor.scala
@@ -17,7 +17,12 @@
 
 package org.apache.daffodil.tdml.processor
 
-import org.apache.daffodil.api.{ DaffodilSchemaSource, DataLocation, ValidationMode, Diagnostic }
+import java.nio.file.Path
+
+import org.apache.daffodil.api.DaffodilSchemaSource
+import org.apache.daffodil.api.DataLocation
+import org.apache.daffodil.api.Diagnostic
+import org.apache.daffodil.api.ValidationMode
 import org.apache.daffodil.externalvars.Binding
 
 object TDML {
@@ -85,6 +90,7 @@ trait TDMLResult {
 trait TDMLParseResult extends TDMLResult {
   def addDiagnostic(failure: Diagnostic): Unit
   def getResult: scala.xml.Node
+  def getBlobPaths: Seq[Path] = Seq.empty
   def currentLocation: DataLocation
 }
 
diff --git a/daffodil-tdml-processor/src/main/scala/org/apache/daffodil/tdml/TDMLInfosetInputter.scala b/daffodil-tdml-processor/src/main/scala/org/apache/daffodil/tdml/TDMLInfosetInputter.scala
index b5a827d..3044b42 100644
--- a/daffodil-tdml-processor/src/main/scala/org/apache/daffodil/tdml/TDMLInfosetInputter.scala
+++ b/daffodil-tdml-processor/src/main/scala/org/apache/daffodil/tdml/TDMLInfosetInputter.scala
@@ -17,11 +17,14 @@
 
 package org.apache.daffodil.tdml
 
+import java.net.URI
+
 import org.apache.daffodil.infoset.InfosetInputter
 import org.apache.daffodil.infoset.ScalaXMLInfosetInputter
 import org.apache.daffodil.infoset.InfosetInputterEventType
 import org.apache.daffodil.infoset.JsonInfosetInputter
 import org.apache.daffodil.util.MaybeBoolean
+import org.apache.daffodil.util.Misc
 import org.apache.daffodil.dpath.NodeInfo
 
 class TDMLInfosetInputter(val scalaInputter: ScalaXMLInfosetInputter, others: Seq[InfosetInputter]) extends InfosetInputter {
@@ -83,7 +86,24 @@ class TDMLInfosetInputter(val scalaInputter: ScalaXMLInfosetInputter, others: Se
 
     if (!othersmatch)
       throw TDMLException("getSimpleText does not match", Some(implString))
-    res
+
+    if (primType.isInstanceOf[NodeInfo.AnyURI.Kind]) {
+      val uri = new URI(res)
+      if (!uri.getPath.startsWith("/")) {
+        // TDML files must allow blob URI's to be relative, but Daffodil
+        // requires them to be absolute with a scheme. So search for the file
+        // using TDML semantics and convert to an absolute URI
+        val abs = Misc.searchResourceOption(uri.getPath, None)
+        if (abs.isEmpty) {
+          throw TDMLException("Unable to find URI: " + res, Some(implString))
+        }
+        abs.get.toString
+      } else {
+        res
+      }
+    } else {
+      res
+    }
   }
 
   override def isNilled(): MaybeBoolean = {
diff --git a/daffodil-tdml-processor/src/main/scala/org/apache/daffodil/tdml/processor/DaffodilTDMLDFDLProcessor.scala b/daffodil-tdml-processor/src/main/scala/org/apache/daffodil/tdml/processor/DaffodilTDMLDFDLProcessor.scala
index 683d9ee..73f4e15 100644
--- a/daffodil-tdml-processor/src/main/scala/org/apache/daffodil/tdml/processor/DaffodilTDMLDFDLProcessor.scala
+++ b/daffodil-tdml-processor/src/main/scala/org/apache/daffodil/tdml/processor/DaffodilTDMLDFDLProcessor.scala
@@ -18,24 +18,28 @@
 package org.apache.daffodil.tdml.processor
 
 import java.nio.channels.Channels
+import java.nio.file.Path
+import java.nio.file.Paths
+
+import scala.xml.Node
 
 import org.apache.daffodil.api._
 import org.apache.daffodil.compiler.Compiler
 import org.apache.daffodil.debugger.Debugger
+import org.apache.daffodil.debugger.InteractiveDebugger
+import org.apache.daffodil.debugger.TraceDebuggerRunner
+import org.apache.daffodil.dsom.ExpressionCompilers
+import org.apache.daffodil.exceptions.Assert
 import org.apache.daffodil.externalvars.Binding
 import org.apache.daffodil.infoset.ScalaXMLInfosetInputter
 import org.apache.daffodil.io.InputSourceDataInputStream
+import org.apache.daffodil.processors.DataProcessor
+import org.apache.daffodil.processors.UnparseResult
 import org.apache.daffodil.processors.unparsers.UState
-import org.apache.daffodil.processors.{ DataProcessor, UnparseResult }
-import org.apache.daffodil.tdml.{ SchemaDataProcessorCache, TDMLInfosetOutputter }
-import org.apache.daffodil.util.MaybeULong
-
-import scala.xml.Node
-import org.apache.daffodil.debugger.InteractiveDebugger
 import org.apache.daffodil.tdml.SchemaDataProcessorCache
-import org.apache.daffodil.debugger.TraceDebuggerRunner
-import org.apache.daffodil.dsom.ExpressionCompilers
-import org.apache.daffodil.exceptions.Assert
+import org.apache.daffodil.tdml.TDMLInfosetInputter
+import org.apache.daffodil.tdml.TDMLInfosetOutputter
+import org.apache.daffodil.util.MaybeULong
 
 final class TDMLDFDLProcessorFactory() extends AbstractTDMLDFDLProcessorFactory {
 
@@ -130,6 +134,10 @@ class DaffodilTDMLDFDLProcessor(ddp: DFDL.DataProcessor) extends TDMLDFDLProcess
 
   private lazy val builtInTracer = new InteractiveDebugger(new TraceDebuggerRunner, ExpressionCompilers)
 
+  private val blobDir = Paths.get(System.getProperty("java.io.tmpdir"), "daffodil-tdml", "blobs")
+  private val blobPrefix = ""
+  private val blobSuffix = ".bin"
+
   override def setDebugging(b: Boolean) = dp.setDebugging(b)
 
   override def setTracing(bool: Boolean): Unit = {
@@ -154,7 +162,9 @@ class DaffodilTDMLDFDLProcessor(ddp: DFDL.DataProcessor) extends TDMLDFDLProcess
   override def getDiagnostics: Seq[Diagnostic] = dp.getDiagnostics
 
   override def parse(is: java.io.InputStream, lengthLimitInBits: Long): TDMLParseResult = {
+
     val outputter = new TDMLInfosetOutputter()
+    outputter.setBlobAttributes(blobDir, blobPrefix, blobSuffix)
 
     val dis = InputSourceDataInputStream(is)
     if (lengthLimitInBits >= 0 && lengthLimitInBits % 8 != 0) {
@@ -171,7 +181,12 @@ class DaffodilTDMLDFDLProcessor(ddp: DFDL.DataProcessor) extends TDMLDFDLProcess
   override def unparse(infosetXML: scala.xml.Node, outStream: java.io.OutputStream): TDMLUnparseResult = {
     val output = java.nio.channels.Channels.newChannel(outStream)
 
-    val inputter = new ScalaXMLInfosetInputter(infosetXML)
+    val scalaInputter = new ScalaXMLInfosetInputter(infosetXML)
+    // We can't compare against other inputters since we only have scala XML,
+    // but we still need to use the TDMLInfosetInputter since it may make TDML
+    // specific modifications to the input infoset (e.g. blob paths)
+    val otherInputters = Seq.empty
+    val inputter = new TDMLInfosetInputter(scalaInputter, otherInputters)
     val actual = dp.unparse(inputter, output).asInstanceOf[UnparseResult]
     output.close()
     new DaffodilTDMLUnparseResult(actual, outStream)
@@ -192,6 +207,8 @@ final class DaffodilTDMLParseResult(actual: DFDL.ParseResult, outputter: TDMLInf
 
   override def getResult: Node = outputter.getResult()
 
+  override def getBlobPaths: Seq[Path] = outputter.getBlobPaths()
+
   def inputter = outputter.toInfosetInputter()
 
   override def isProcessingError: Boolean = actual.isProcessingError
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/Blobs.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/Blobs.tdml
new file mode 100644
index 0000000..a9f96c4
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/Blobs.tdml
@@ -0,0 +1,349 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<tdml:testSuite suiteName="SimpleTypes" description="Section 5 - simple types"
+  xmlns:daf="urn:ogf:dfdl:2013:imp:daffodil.apache.org:2018:ext"
+  xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/"
+  xmlns:dfdlx="http://www.ogf.org/dfdl/dfdl-1.0/extensions"
+  xmlns:ex="http://example.com"
+  xmlns:tdml="http://www.ibm.com/xmlns/dfdl/testData"
+  xmlns:tns="http://example.com"
+  xmlns:xs="http://www.w3.org/2001/XMLSchema"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  defaultRoundTrip="onePass"
+  defaultConfig="blobChunkSize">
+
+  <!-- set the blob chunking size to 1 hopefully catch edge cases -->
+  <tdml:defineConfig name="blobChunkSize">
+    <daf:tunables>
+      <daf:blobChunkSizeInBytes>1</daf:blobChunkSizeInBytes>
+    </daf:tunables>
+  </tdml:defineConfig>
+
+
+  <tdml:defineSchema name="Blob.dfdl.xsd">
+
+    <xs:include schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+
+    <dfdl:format ref="ex:GeneralFormatPortable"
+      representation="binary"
+      encodingErrorPolicy="replace" />
+
+    <xs:element name="blob_01" type="xs:anyURI"
+      dfdl:lengthKind="explicit" dfdl:length="4"
+      dfdlx:objectKind="bytes" />
+
+    <xs:element name="blob_02" type="xs:anyURI"
+      dfdl:lengthKind="explicit" dfdl:length="30" dfdl:lengthUnits="bits"
+      dfdl:bitOrder="mostSignificantBitFirst"
+      dfdlx:objectKind="bytes" />
+
+    <xs:element name="blob_03" type="xs:anyURI"
+      dfdl:lengthKind="explicit" dfdl:length="30" dfdl:lengthUnits="bits"
+      dfdl:bitOrder="leastSignificantBitFirst" dfdl:byteOrder="littleEndian"
+      dfdlx:objectKind="bytes" />
+
+    <xs:element name="blob_04">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="length" type="xs:int" dfdl:lengthKind="explicit" dfdl:length="4"
+            dfdl:outputValueCalc="{ dfdl:valueLength(../ex:data, 'bytes') }" />
+          <xs:element name="data" type="xs:anyURI"
+            dfdl:lengthKind="explicit" dfdl:length="{ ../ex:length }"
+            dfdlx:objectKind="bytes" />
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="blob_05">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="length" type="xs:int" dfdl:lengthKind="explicit" dfdl:length="4"
+            dfdl:outputValueCalc="{ dfdl:valueLength(../ex:data, 'bytes') }" />
+          <xs:element name="data" type="xs:anyURI"
+            dfdl:lengthKind="explicit" dfdl:length="{ ../ex:length }"
+            dfdlx:objectKind="bytes" />
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="blob_06" type="xs:anyURI"
+      dfdl:lengthKind="explicit" dfdl:length="4" dfdl:lengthUnits="characters"
+      dfdlx:objectKind="bytes" />
+
+    <xs:element name="blob_07" type="xs:anyURI"
+      dfdl:lengthKind="implicit"
+      dfdlx:objectKind="bytes" />
+
+    <xs:element name="blob_08">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="a" type="xs:anyURI"
+            dfdl:lengthKind="explicit" dfdl:length="1"
+            dfdlx:objectKind="bytes" />
+          <xs:choice>
+            <xs:sequence>
+              <xs:element name="b" type="xs:anyURI"
+                dfdl:lengthKind="explicit" dfdl:length="2"
+                dfdlx:objectKind="bytes" />
+              <xs:element name="c" type="xs:anyURI"
+                dfdl:lengthKind="explicit" dfdl:length="2"
+                dfdlx:objectKind="bytes" />
+            </xs:sequence>
+            <xs:element name="d" type="xs:anyURI"
+              dfdl:lengthKind="explicit" dfdl:length="3"
+              dfdlx:objectKind="bytes" />
+          </xs:choice>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="blob_09">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="a" type="xs:anyURI"
+            dfdl:lengthKind="explicit" dfdl:length="1"
+            dfdlx:objectKind="bytes" />
+          <xs:element name="len" type="xs:int" dfdl:lengthKind="explicit" dfdl:length="1" />
+          <xs:element name="b" type="xs:anyURI"
+            dfdl:lengthKind="explicit" dfdl:length="{ ../ex:len }"
+            dfdlx:objectKind="bytes" />
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="clob_01" type="xs:anyURI"
+      dfdl:lengthKind="explicit" dfdl:length="4"
+      dfdlx:objectKind="chars" />
+    
+  </tdml:defineSchema>
+
+
+  <!-- 4 bytes of blob data -->
+  <tdml:parserTestCase name="blob_01" root="blob_01" model="Blob.dfdl.xsd">
+    <tdml:document>
+      <tdml:documentPart type="byte"><![CDATA[a1b1c1d1]]></tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:blob_01 xsi:type="xs:anyURI">org/apache/daffodil/section05/simple_types/blobs/blob_01.bin</ex:blob_01>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+  
+  <!-- 30 bits of blob data, MSBF -->
+  <tdml:parserTestCase name="blob_02" root="blob_02" model="Blob.dfdl.xsd">
+    <tdml:document>
+      <tdml:documentPart type="bits"><![CDATA[11011110 10101101 10111110 111011xx]]></tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:blob_02 xsi:type="xs:anyURI">org/apache/daffodil/section05/simple_types/blobs/blob_02.bin</ex:blob_02>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <!-- 30 bits of blob data, LSBF -->
+  <tdml:parserTestCase name="blob_03" root="blob_03" model="Blob.dfdl.xsd">
+    <tdml:document bitOrder="LSBFirst">
+      <tdml:documentPart type="bits" byteOrder="RTL"><![CDATA[xx101111 10111110 10101101 11011110]]></tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:blob_03 xsi:type="xs:anyURI">org/apache/daffodil/section05/simple_types/blobs/blob_03.bin</ex:blob_03>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <!-- unparse 3 bytes of blob data with 1 fill byte -->
+  <tdml:unparserTestCase name="blob_04" root="blob_01" model="Blob.dfdl.xsd" roundTrip="none">
+    <tdml:document>
+      <tdml:documentPart type="byte"><![CDATA[a1b1c120]]></tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:blob_01 xsi:type="xs:anyURI">org/apache/daffodil/section05/simple_types/blobs/blob_04.bin</ex:blob_01>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:unparserTestCase>
+
+  <!-- dynamic number of blob data, parse -->
+  <tdml:parserTestCase name="blob_05" root="blob_04" model="Blob.dfdl.xsd">
+    <tdml:document>
+      <tdml:documentPart type="byte"><![CDATA[00 00 00 04 a1 b1 c1 d1]]></tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:blob_04>
+          <ex:length>4</ex:length>
+          <ex:data xsi:type="xs:anyURI">org/apache/daffodil/section05/simple_types/blobs/blob_01.bin</ex:data>
+        </ex:blob_04>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <!-- dynamic number of blob data, unparse -->
+  <tdml:unparserTestCase name="blob_06" root="blob_04" model="Blob.dfdl.xsd" roundTrip="false">
+    <tdml:document>
+      <tdml:documentPart type="byte"><![CDATA[00 00 00 04 a1 b1 c1 d1]]></tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:blob_04>
+          <ex:length>0</ex:length>
+          <ex:data xsi:type="xs:anyURI">org/apache/daffodil/section05/simple_types/blobs/blob_01.bin</ex:data>
+        </ex:blob_04>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:unparserTestCase>
+
+  <!-- unparsed data longer that length -->
+  <tdml:unparserTestCase name="blob_07" root="blob_01" model="Blob.dfdl.xsd">
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:blob_01 xsi:type="xs:anyURI">org/apache/daffodil/section05/simple_types/blobs/blob_07.bin</ex:blob_01>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:errors>
+      <tdml:error>Unparse Error</tdml:error>
+      <tdml:error>Blob length</tdml:error>
+      <tdml:error>40 bits</tdml:error>
+      <tdml:error>exceeds</tdml:error>
+      <tdml:error>32 bits</tdml:error>
+    </tdml:errors>
+  </tdml:unparserTestCase>
+
+  <!-- unparse, blob file not found -->
+  <tdml:unparserTestCase name="blob_08" root="blob_01" model="Blob.dfdl.xsd">
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:blob_01 xsi:type="xs:anyURI">file:/this/file/does/not/exist</ex:blob_01>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:errors>
+      <tdml:error>Unparse Error</tdml:error>
+      <tdml:error>Unable to open blob for reading</tdml:error>
+      <tdml:error>file:/this/file/does/not/exist</tdml:error>
+    </tdml:errors>
+  </tdml:unparserTestCase>
+
+  <!-- unparse, blob must be a file -->
+  <tdml:unparserTestCase name="blob_09" root="blob_01" model="Blob.dfdl.xsd">
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:blob_01 xsi:type="xs:anyURI">http://scheme/not/allowed</ex:blob_01>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:errors>
+      <tdml:error>Unparse Error</tdml:error>
+      <tdml:error>Blob URI must be a file</tdml:error>
+      <tdml:error>http://scheme/not/allowed</tdml:error>
+    </tdml:errors>
+  </tdml:unparserTestCase>
+
+  <!-- parse, not enough bits -->
+  <tdml:parserTestCase name="blob_10" root="blob_01" model="Blob.dfdl.xsd">
+    <tdml:document>
+      <tdml:documentPart type="byte"><![CDATA[a1b1c1]]></tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Parse Error</tdml:error>
+      <tdml:error>Insufficient bits in data</tdml:error>
+      <tdml:error>Needed 32 bit(s)</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <!-- sde, invalid lengthUnits -->
+  <tdml:parserTestCase name="blob_11" root="blob_06" model="Blob.dfdl.xsd">
+    <tdml:document>
+      <tdml:documentPart type="byte"><![CDATA[a1b1c1]]></tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>lengthKind 'explicit'</tdml:error>
+      <tdml:error>lengthUnits 'characters'</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <!-- sde, invalid lengthKind -->
+  <tdml:parserTestCase name="blob_12" root="blob_07" model="Blob.dfdl.xsd">
+    <tdml:document>
+      <tdml:documentPart type="byte"><![CDATA[a1b1c1]]></tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>objectKind='bytes'</tdml:error>
+      <tdml:error>must have dfdl:lengthKind='explicit'</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <!-- 4 bytes of blob data split in multiple elements with a choice, backtracking should remove blobs -->
+  <tdml:parserTestCase name="blob_13" root="blob_08" model="Blob.dfdl.xsd">
+    <tdml:document>
+      <tdml:documentPart type="byte"><![CDATA[a1b1c1d1]]></tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:blob_08>
+          <ex:a xsi:type="xs:anyURI">org/apache/daffodil/section05/simple_types/blobs/blob_13a.bin</ex:a>
+          <ex:d xsi:type="xs:anyURI">org/apache/daffodil/section05/simple_types/blobs/blob_13d.bin</ex:d>
+        </ex:blob_08>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <!-- 4 bytes of blob data split in multiple elements with a choice, no backtracking needed -->
+  <tdml:parserTestCase name="blob_14" root="blob_08" model="Blob.dfdl.xsd">
+    <tdml:document>
+      <tdml:documentPart type="byte"><![CDATA[a1b1c1d1e1]]></tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ex:blob_08>
+          <ex:a xsi:type="xs:anyURI">org/apache/daffodil/section05/simple_types/blobs/blob_13a.bin</ex:a>
+          <ex:b xsi:type="xs:anyURI">org/apache/daffodil/section05/simple_types/blobs/blob_13b.bin</ex:b>
+          <ex:c xsi:type="xs:anyURI">org/apache/daffodil/section05/simple_types/blobs/blob_13c.bin</ex:c>
+        </ex:blob_08>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <!-- 1 byte of blob, length, 3 bytes of blob, but invalid len caused SDE, blob should all be deleted -->
+  <tdml:parserTestCase name="blob_15" root="blob_09" model="Blob.dfdl.xsd">
+    <tdml:document>
+      <tdml:documentPart type="byte"><![CDATA[a1ffb1c1]]></tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Runtime Schema Definition Error</tdml:error>
+      <tdml:error>dfdl:length expression result must be non-negative</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <!-- clob unsupported -->
+  <tdml:parserTestCase name="clob_01" root="clob_01" model="Blob.dfdl.xsd">
+    <tdml:document>
+      <tdml:documentPart type="text">clobdata</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>objectKind='chars' is not supported.</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+
+</tdml:testSuite>
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/SimpleTypes.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/SimpleTypes.tdml
index 99d0213..d1779a2 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/SimpleTypes.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/SimpleTypes.tdml
@@ -19,6 +19,7 @@
 <tdml:testSuite suiteName="SimpleTypes" description="Section 5 - simple types"
   xmlns:tdml="http://www.ibm.com/xmlns/dfdl/testData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/" xmlns:xs="http://www.w3.org/2001/XMLSchema"
+  xmlns:dfdlx="http://www.ogf.org/dfdl/dfdl-1.0/extensions"
   xmlns:ex="http://example.com" xmlns:tns="http://example.com"
   defaultRoundTrip="true">
 
@@ -3997,7 +3998,7 @@
       </tdml:dfdlInfoset>
     </tdml:infoset>
   </tdml:parserTestCase>
-  
+ 
   <!--
     Test name: hexBinary_rep
     Schema: HexBinary
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_01.bin b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_01.bin
new file mode 100644
index 0000000..c6e0510
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_01.bin
@@ -0,0 +1 @@
+����
\ No newline at end of file
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_02.bin b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_02.bin
new file mode 100644
index 0000000..01904d7
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_02.bin
@@ -0,0 +1 @@
+ޭ��
\ No newline at end of file
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_03.bin b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_03.bin
new file mode 100644
index 0000000..39236d8
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_03.bin
@@ -0,0 +1 @@
+ޭ�/
\ No newline at end of file
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_04.bin b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_04.bin
new file mode 100644
index 0000000..ad90cb5
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_04.bin
@@ -0,0 +1 @@
+���
\ No newline at end of file
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_07.bin b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_07.bin
new file mode 100644
index 0000000..d26a6fe
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_07.bin
@@ -0,0 +1 @@
+�����
\ No newline at end of file
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_13a.bin b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_13a.bin
new file mode 100644
index 0000000..57814dd
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_13a.bin
@@ -0,0 +1 @@
+�
\ No newline at end of file
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_13b.bin b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_13b.bin
new file mode 100644
index 0000000..537a638
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_13b.bin
@@ -0,0 +1 @@
+��
\ No newline at end of file
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_13c.bin b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_13c.bin
new file mode 100644
index 0000000..3ad68b7
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_13c.bin
@@ -0,0 +1 @@
+��
\ No newline at end of file
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_13d.bin b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_13d.bin
new file mode 100644
index 0000000..2a5e62b
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/blobs/blob_13d.bin
@@ -0,0 +1 @@
+���
\ No newline at end of file
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section05/simple_types/TestBlobs.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section05/simple_types/TestBlobs.scala
new file mode 100644
index 0000000..4438043
--- /dev/null
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/section05/simple_types/TestBlobs.scala
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.daffodil.section05.simple_types
+
+import org.junit._
+import org.apache.daffodil.tdml.Runner
+import org.junit.AfterClass
+
+object TestBlobs {
+
+  val testDir = "/org/apache/daffodil/section05/simple_types/"
+
+  val runner = Runner(testDir, "Blobs.tdml")
+
+  @AfterClass def shutDown() {
+    runner.reset
+  }
+}
+
+class TestBlobs {
+  import TestBlobs._
+
+  @Test def test_blob_01() { runner.runOneTest("blob_01") }
+  @Test def test_blob_02() { runner.runOneTest("blob_02") }
+  @Test def test_blob_03() { runner.runOneTest("blob_03") }
+  @Test def test_blob_04() { runner.runOneTest("blob_04") }
+  @Test def test_blob_05() { runner.runOneTest("blob_05") }
+  @Test def test_blob_06() { runner.runOneTest("blob_06") }
+  @Test def test_blob_07() { runner.runOneTest("blob_07") }
+  @Test def test_blob_08() { runner.runOneTest("blob_08") }
+  @Test def test_blob_09() { runner.runOneTest("blob_09") }
+  @Test def test_blob_10() { runner.runOneTest("blob_10") }
+  @Test def test_blob_11() { runner.runOneTest("blob_11") }
+  @Test def test_blob_12() { runner.runOneTest("blob_12") }
+  @Test def test_blob_13() { runner.runOneTest("blob_13") }
+  @Test def test_blob_14() { runner.runOneTest("blob_14") }
+  @Test def test_blob_15() { runner.runOneTest("blob_15") }
+
+  @Test def test_clob_01() { runner.runOneTest("clob_01") }
+
+}