diff --git a/scapy/contrib/coap.py b/scapy/contrib/coap.py
index 9c8bf65aafbea625621effa6edfe0bd3b3b9abfe..fbd67d20eb2a04aedce5ec9a687192ed541cec8f 100644
--- a/scapy/contrib/coap.py
+++ b/scapy/contrib/coap.py
@@ -157,8 +157,13 @@ class _CoAPOptsField(StrField):
     def i2h(self, pkt, x):
         return [(coap_options[0][o[0]], o[1]) if o[0] in coap_options[0] else o for o in x]
 
+    # consume only the coap layer from the wire string
     def getfield(self, pkt, s):
-        return "", self.m2i(pkt, s)
+        opts = self.m2i(pkt, s)
+        used = 0
+        for o in opts:
+            used += o[0]
+        return s[used:], [ (o[1], o[2]) for o in opts ]
 
     def m2i(self, pkt, x):
         opts = []
@@ -166,7 +171,9 @@ class _CoAPOptsField(StrField):
         cur_delta = 0
         while isinstance(o, _CoAPOpt):
             cur_delta += _get_abs_val(o.delta, o.delta_ext)
-            opts.append((cur_delta, o.opt_val))
+            # size of this option in bytes
+            u = 1 + len(o.opt_val) + len(o.delta_ext) + len(o.len_ext)
+            opts.append((u, cur_delta, o.opt_val))
             o = o.payload
         return opts
 
@@ -189,8 +196,26 @@ class _CoAPOptsField(StrField):
 
         return str(opts)
 
+class _CoAPPaymark(StrField):
+
+    def i2h(self, pkt, x):
+        return x
+
+    def getfield(self, pkt, s):
+        (u, m) = self.m2i(pkt, s)
+        return s[u:], m
+
+    def m2i(self, pkt, x):
+        if len(x) > 0 and x[0] == '\xff':
+            return 1, '\xff'
+        return 0, '';
+
+    def i2m(self, pkt, x):
+        return x
+
 
 class CoAP(Packet):
+    __slots__ = ["content_format"]
     name = "CoAP"
 
     fields_desc = [BitField("ver", 1, 2),
@@ -199,9 +224,21 @@ class CoAP(Packet):
                    ByteEnumField("code", 0, coap_codes),
                    ShortField("msg_id", 0),
                    StrLenField("token", "", length_from=lambda pkt: pkt.tkl),
-                   _CoAPOptsField("options", [])
+                   _CoAPOptsField("options", []),
+                   _CoAPPaymark("paymark", "")
                    ]
 
+    def getfieldval(self, attr):
+        v = getattr(self, attr)
+        if v:
+            return v
+        return Packet.getfieldval(self, attr)
+
+    def post_dissect(self, pay):
+        for k in self.options:
+            if k[0] == "Content-Format":
+                self.content_format = k[1]
+        return pay
 
 bind_layers(UDP, CoAP, sport=5683)
 bind_layers(UDP, CoAP, dport=5683)
diff --git a/scapy/contrib/coap.uts b/scapy/contrib/coap.uts
index aa83c33952d525acf55c5dbfca2a69f98f8485b7..236a30261e33fb9389f7f5184ac9e8cfe55a8014 100644
--- a/scapy/contrib/coap.uts
+++ b/scapy/contrib/coap.uts
@@ -6,39 +6,39 @@ from scapy.contrib.coap import *
 
 + Test CoAP
 = CoAP default values
-str(CoAP()) == '\x40\x00\x00\x00'
+assert(str(CoAP()) == '\x40\x00\x00\x00')
 
 = Token length calculation
 p = CoAP(token='foobar')
-CoAP(str(p)).tkl == 6
+assert(CoAP(str(p)).tkl == 6)
 
 = CON GET dissect
 p = CoAP('\x40\x01\xd9\xe1\xbb\x2e\x77\x65\x6c\x6c\x2d\x6b\x6e\x6f\x77\x6e\x04\x63\x6f\x72\x65')
-p.code == 1
-p.ver == 1
-p.tkl == 0
-p.tkl == 0
-p.msg_id = 55777
-p.token == ''
-p.type == 0
-p.options == [('Uri-Path', '.well-known'), ('Uri-Path', 'core')]
+assert(p.code == 1)
+assert(p.ver == 1)
+assert(p.tkl == 0)
+assert(p.tkl == 0)
+assert(p.msg_id == 55777)
+assert(p.token == '')
+assert(p.type == 0)
+assert(p.options == [('Uri-Path', '.well-known'), ('Uri-Path', 'core')])
 
 = Extended option delta
-str(CoAP(options=[("Uri-Query", "query")])) == '\x40\x00\x00\x00\xd5\x02\x71\x75\x65\x72\x79'
+assert(str(CoAP(options=[("Uri-Query", "query")])) == '\x40\x00\x00\x00\xd5\x02\x71\x75\x65\x72\x79')
 
 = Extended option length
-str(CoAP(options=[("Location-Path", 'x' * 280)])) == '\x40\x00\x00\x00\x8e\x0b\x00' + '\x78' * 280
+assert(str(CoAP(options=[("Location-Path", 'x' * 280)])) == '\x40\x00\x00\x00\x8e\x0b\x00' + '\x78' * 280)
 
 = Options should be ordered by option number
-str(CoAP(options=[("Uri-Query", "b"),("Uri-Path","a")])) == '\x40\x00\x00\x00\xb1\x61\x41\x62'
+assert(str(CoAP(options=[("Uri-Query", "b"),("Uri-Path","a")])) == '\x40\x00\x00\x00\xb1\x61\x41\x62')
 
 = Options of the same type should not be reordered
-str(CoAP(options=[("Uri-Path", "b"),("Uri-Path","a")])) == '\x40\x00\x00\x00\xb1\x62\x01\x61'
+assert(str(CoAP(options=[("Uri-Path", "b"),("Uri-Path","a")])) == '\x40\x00\x00\x00\xb1\x62\x01\x61')
 
 + Test layer binding
 = Destination port
 p = UDP()/CoAP()
-p[UDP].dport == 5683
+assert(p[UDP].dport == 5683)
 
 = Source port
 s = '\x16\x33\xa0\xa4\x00\x78\xfe\x8b\x60\x45\xd9\xe1\xc1\x28\xff\x3c\x2f\x3e\x3b\x74\x69\x74\x6c\x65\x3d\x22\x47\x65' \
@@ -46,4 +46,18 @@ s = '\x16\x33\xa0\xa4\x00\x78\xfe\x8b\x60\x45\xd9\xe1\xc1\x28\xff\x3c\x2f\x3e\x3
     '\x22\x63\x6c\x6f\x63\x6b\x22\x3b\x72\x74\x3d\x22\x54\x69\x63\x6b\x73\x22\x3b\x74\x69\x74\x6c\x65\x3d\x22\x49\x6e' \
     '\x74\x65\x72\x6e\x61\x6c\x20\x43\x6c\x6f\x63\x6b\x22\x3b\x63\x74\x3d\x30\x3b\x6f\x62\x73\x2c\x3c\x2f\x61\x73\x79' \
     '\x6e\x63\x3e\x3b\x63\x74\x3d\x30'
-CoAP in UDP(s)
+assert(CoAP in UDP(s))
+
+= building with a text/plain payload
+p = CoAP(ver = 1, type = 0, code = 0x42, msg_id = 0xface, options=[("Content-Format", "\x00")], paymark = "\xff")
+p /= Raw("\xde\xad\xbe\xef")
+assert(str(p) == '\x40\x42\xfa\xce\xc1\x00\xff\xde\xad\xbe\xef')
+
+= dissection with a text/plain payload
+p = CoAP(str(p))
+assert(p.ver == 1)
+assert(p.type == 0)
+assert(p.code == 0x42)
+assert(p.msg_id == 0xface)
+assert(isinstance(p.payload, Raw))
+assert(p.payload.load == '\xde\xad\xbe\xef')