Repository: secdev/scapy

Issue:
Syntax errors in Packet.command output when using VolatileValue
Hi all,

VolatileValue class is not handled properly in Packet.command method. This results in syntax errors when using output of command method.

#### Environment

-   Scapy version: Latest (9486bb2cef76)
-   Python version: 3.7.3
-   Operating System: Debian 10

#### How to reproduce

```
>>> eval(ICMPv6NIQueryNOOP().command())                                                                                                                       
Traceback (most recent call last):

  File "/usr/local/lib/python3.7/dist-packages/IPython/core/interactiveshell.py", line 3326, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)

  File "<ipython-input-1-b772ab6aa3ea>", line 1, in <module>
    eval(ICMPv6NIQueryNOOP().command())

  File "<string>", line 1
    ICMPv6NIQueryNOOP(nonce=<RandBin>)
                            ^
SyntaxError: invalid syntax
```

#### Expected result

This should return an instance of ICMPv6NIQueryNOOP. From v2.4.0:
```
>>> eval(ICMPv6NIQueryNOOP().command())
<ICMPv6NIQueryNOOP  |>
```

#### Related resources

A git bisect gave me this commit:
de079743169414141b24026c656eace4d33e7d78
<!-- traces / sample pcaps (stripped to the relevant frames), related standards, RFCs or other resources -->


I can try to fix this, but I am not sure what should be done. Here are solutions I could think of:

 * Generate the value (call to _fix method right?): ICMPv6NIQueryNOOP(nonce='\x21...')
 * return the string required to instance another VolatileValue: ICMPv6NIQueryNOOP(nonce=RandBin())

The first solution loses the 'randomness' for the nonce, which can be bad (?). I don't know nonce for ICMPv6 but I know they are important for security reasons in IPsec so...
The second solution can crash if the VolatileValue is not imported prior to evaluation of the command output. Which is not an issue if using run_scapy or scapy.all.

Keep me posted if you want me to fix this ;-)


Discussion:

Resolving patch:
diff --git a/scapy/packet.py b/scapy/packet.py
--- a/scapy/packet.py
+++ b/scapy/packet.py
@@ -1466,6 +1466,8 @@ def command(self):
                 fv = "[%s]" % ",".join(map(Packet.command, fv))
             elif isinstance(fld, FlagsField):
                 fv = int(fv)
+            elif callable(getattr(fv, 'command', None)):
+                fv = fv.command()
             else:
                 fv = repr(fv)
             f.append("%s=%s" % (fn, fv))
diff --git a/scapy/volatile.py b/scapy/volatile.py
--- a/scapy/volatile.py
+++ b/scapy/volatile.py
@@ -85,6 +85,12 @@ class VolatileValue(object):
     def __repr__(self):
         return "<%s>" % self.__class__.__name__
 
+    def _command_args(self):
+        return ''
+
+    def command(self):
+        return "%s(%s)" % (self.__class__.__name__, self._command_args())
+
     def __eq__(self, other):
         x = self._fix()
         y = other._fix() if isinstance(other, VolatileValue) else other
@@ -198,6 +204,11 @@ def __init__(self, min, max):
         self.min = min
         self.max = max
 
+    def _command_args(self):
+        if self.__class__.__name__ == 'RandNum':
+            return "min=%r, max=%r" % (self.min, self.max)
+        return super(RandNum, self)._command_args()
+
     def _fix(self):
         return random.randrange(self.min, self.max + 1)
 
@@ -217,6 +228,9 @@ def __init__(self, alpha, beta):
         self.alpha = alpha
         self.beta = beta
 
+    def _command_args(self):
+        return "alpha=%r, beta=%r" % (self.alpha, self.beta)
+
     def _fix(self):
         return int(round(random.gammavariate(self.alpha, self.beta)))
 
@@ -226,6 +240,9 @@ def __init__(self, mu, sigma):
         self.mu = mu
         self.sigma = sigma
 
+    def _command_args(self):
+        return "mu=%r, sigma=%r" % (self.mu, self.sigma)
+
     def _fix(self):
         return int(round(random.gauss(self.mu, self.sigma)))
 
@@ -235,6 +252,12 @@ def __init__(self, lambd, base=0):
         self.lambd = lambd
         self.base = base
 
+    def _command_args(self):
+        ret = "lambd=%r" % self.lambd
+        if self.base != 0:
+            ret += ", base=%r" % self.base
+        return ret
+
     def _fix(self):
         return self.base + int(round(random.expovariate(self.lambd)))
 
@@ -243,9 +266,16 @@ class RandEnum(RandNum):
     """Instances evaluate to integer sampling without replacement from the given interval"""  # noqa: E501
 
     def __init__(self, min, max, seed=None):
+        self._seed = seed
         self.seq = RandomEnumeration(min, max, seed)
         super(RandEnum, self).__init__(min, max)
 
+    def _command_args(self):
+        ret = "min=%r, max=%r" % (self.min, self.max)
+        if self._seed:
+            ret += ", seed=%r" % self._seed
+        return ret
+
     def _fix(self):
         return next(self.seq)
 
@@ -337,6 +367,13 @@ def __init__(self, enum, seed=None):
         self.enum = list(enum)
         RandEnum.__init__(self, 0, len(self.enum) - 1, seed)
 
+    def _command_args(self):
+        # Note: only outputs the list of keys, but values are irrelevant anyway
+        ret = "enum=%r" % self.enum
+        if self._seed:
+            ret += ", seed=%r" % self._seed
+        return ret
+
     def _fix(self):
         return self.enum[next(self.seq)]
 
@@ -347,17 +384,34 @@ def __init__(self, *args):
             raise TypeError("RandChoice needs at least one choice")
         self._choice = list(args)
 
+    def _command_args(self):
+        return ", ".join(self._choice)
+
     def _fix(self):
         return random.choice(self._choice)
 
 
 class RandString(RandField):
-    def __init__(self, size=None, chars=b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"):  # noqa: E501
+    _DEFAULT_CHARS = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"  # noqa: E501
+
+    def __init__(self, size=None, chars=_DEFAULT_CHARS):
         if size is None:
             size = RandNumExpo(0.01)
         self.size = size
         self.chars = chars
 
+    def _command_args(self):
+        ret = ""
+        if isinstance(self.size, VolatileValue):
+            if self.size.lambd != 0.01 or self.size.base != 0:
+                ret += "size=%r" % self.size.command()
+        else:
+            ret += "size=%r" % self.size
+
+        if self.chars != self._DEFAULT_CHARS:
+            ret += ", chars=%r" % self.chars
+        return ret
+
     def _fix(self):
         s = b""
         for _ in range(self.size):
@@ -379,21 +433,42 @@ class RandBin(RandString):
     def __init__(self, size=None):
         super(RandBin, self).__init__(size=size, chars=b"".join(chb(c) for c in range(256)))  # noqa: E501
 
+    def _command_args(self):
+        if not isinstance(self.size, VolatileValue):
+            return "size=%r" % self.size
+
+        if isinstance(self.size, RandNumExpo) and \
+                self.size.lambd == 0.01 and self.size.base == 0:
+            # Default size for RandString, skip
+            return ""
+        return "size=%r" % self.size.command()
+
 
 class RandTermString(RandBin):
     def __init__(self, size, term):
         self.term = bytes_encode(term)
         super(RandTermString, self).__init__(size=size)
 
+    def _command_args(self):
+        return ", ".join((super(RandTermString, self)._command_args(),
+                          "term=%r" % self.term))
+
     def _fix(self):
         return RandBin._fix(self) + self.term
 
 
 class RandIP(RandString):
-    def __init__(self, iptemplate="0.0.0.0/0"):
+    _DEFAULT_IPTEMPLATE = "0.0.0.0/0"
+
+    def __init__(self, iptemplate=_DEFAULT_IPTEMPLATE):
         RandString.__init__(self)
         self.ip = Net(iptemplate)
 
+    def _command_args(self):
+        if self.ip.repr == self._DEFAULT_IPTEMPLATE:
+            return ""
+        return "iptemplate=%r" % self.ip.repr
+
     def _fix(self):
         return self.ip.choice()
 
@@ -401,6 +476,7 @@ def _fix(self):
 class RandMAC(RandString):
     def __init__(self, template="*"):
         RandString.__init__(self)
+        self._template = template
         template += ":*:*:*:*:*"
         template = template.split(":")
         self.mac = ()
@@ -414,6 +490,11 @@ def __init__(self, template="*"):
                 v = int(template[i], 16)
             self.mac += (v,)
 
+    def _command_args(self):
+        if self._template == "*":
+            return ""
+        return "template=%r" % self._template
+
     def _fix(self):
         return "%02x:%02x:%02x:%02x:%02x:%02x" % self.mac
 
@@ -444,6 +525,11 @@ def __init__(self, ip6template="**"):
         self.variable = "" in self.sp
         self.multi = self.sp.count("**")
 
+    def _command_args(self):
+        if self.tmpl == "**":
+            return ""
+        return "ip6template=%r" % self.tmpl
+
     def _fix(self):
         nbm = self.multi
         ip = []
@@ -485,6 +571,25 @@ def __init__(self, fmt=None, depth=RandNumExpo(0.1), idnum=RandNumExpo(0.01)):
         self.depth = depth
         self.idnum = idnum
 
+    def _command_args(self):
+        ret = []
+        if self.fmt:
+            ret.append("fmt=%r" % self.ori_fmt)
+
+        if not isinstance(self.depth, VolatileValue):
+            ret.append("depth=%r" % self.depth)
+        elif not isinstance(self.depth, RandNumExpo) or \
+                self.depth.lambd != 0.1 or self.depth.base != 0:
+            ret.append("depth=%s" % self.depth.command())
+
+        if not isinstance(self.idnum, VolatileValue):
+            ret.append("idnum=%r" % self.idnum)
+        elif not isinstance(self.idnum, RandNumExpo) or \
+                self.idnum.lambd != 0.01 or self.idnum.base != 0:
+            ret.append("idnum=%s" % self.idnum.command())
+
+        return ", ".join(ret)
+
     def __repr__(self):
         if self.ori_fmt is None:
             return "<%s>" % self.__class__.__name__
@@ -513,6 +618,12 @@ def __init__(self, regexp, lambda_=0.3,):
         self._regexp = regexp
         self._lambda = lambda_
 
+    def _command_args(self):
+        ret = "regexp=%r" % self._regexp
+        if self._lambda != 0.3:
+            ret += ", lambda_=%r" % self._lambda
+        return ret
+
     special_sets = {
         "[:alnum:]": "[a-zA-Z0-9]",
         "[:alpha:]": "[a-zA-Z]",
@@ -700,6 +811,8 @@ def make_power_of_two(end):
         return {sign * 2**i for i in range(end_n)}
 
     def __init__(self, mn, mx):
+        self._mn = mn
+        self._mx = mx
         sing = {0, mn, mx, int((mn + mx) / 2)}
         sing |= self.make_power_of_two(mn)
         sing |= self.make_power_of_two(mx)
@@ -712,6 +825,11 @@ def __init__(self, mn, mx):
         super(RandSingNum, self).__init__(*sing)
         self._choice.sort()
 
+    def _command_args(self):
+        if self.__class__.__name__ == 'RandSingNum':
+            return "mn=%r, mx=%r" % (self._mn, self._mx)
+        return super(RandSingNum, self)._command_args()
+
 
 class RandSingByte(RandSingNum):
     def __init__(self):
@@ -811,6 +929,9 @@ def __init__(self):
                         "foo.exe\\", ]
         super(RandSingString, self).__init__(*choices_list)
 
+    def _command_args(self):
+        return ""
+
     def __str__(self):
         return str(self._fix())
 
@@ -821,6 +942,7 @@ def __bytes__(self):
 class RandPool(RandField):
     def __init__(self, *args):
         """Each parameter is a volatile object or a couple (volatile object, weight)"""  # noqa: E501
+        self._args = args
         pool = []
         for p in args:
             w = 1
@@ -829,6 +951,15 @@ def __init__(self, *args):
             pool += [p] * w
         self._pool = pool
 
+    def _command_args(self):
+        ret = []
+        for p in self._args:
+            if isinstance(p, tuple):
+                ret.append("(%s, %r)" % (p[0].command(), p[1]))
+            else:
+                ret.append(p.command())
+        return ", ".join(ret)
+
     def _fix(self):
         r = random.choice(self._pool)
         return r._fix()
@@ -872,10 +1003,14 @@ class RandUUID(RandField):
 
     def __init__(self, template=None, node=None, clock_seq=None,
                  namespace=None, name=None, version=None):
+        self._template = template
+        self._ori_version = version
+
         self.uuid_template = None
         self.node = None
         self.clock_seq = None
         self.namespace = None
+        self.name = None
         self.node = None
         self.version = None
 
@@ -941,6 +1076,22 @@ def __init__(self, template=None, node=None, clock_seq=None,
                                      "did not specify version, you need to "
                                      "specify it explicitly.")
 
+    def _command_args(self):
+        ret = []
+        if self._template:
+            ret.append("template=%r" % self._template)
+        if self.node:
+            ret.append("node=%r" % self.node)
+        if self.clock_seq:
+            ret.append("clock_seq=%r" % self.clock_seq)
+        if self.namespace:
+            ret.append("namespace=%r" % self.namespace)
+        if self.name:
+            ret.append("name=%r" % self.name)
+        if self._ori_version:
+            ret.append("version=%r" % self._ori_version)
+        return ", ".join(ret)
+
     def _fix(self):
         if self.uuid_template:
             return uuid.UUID(("%08x%04x%04x" + ("%02x" * 8))
@@ -962,6 +1113,9 @@ def _fix(self):
 
 class AutoTime(_RandNumeral):
     def __init__(self, base=None, diff=None):
+        self._base = base
+        self._ori_diff = diff
+
         if diff is not None:
             self.diff = diff
         elif base is None:
@@ -969,6 +1123,14 @@ def __init__(self, base=None, diff=None):
         else:
             self.diff = time.time() - base
 
+    def _command_args(self):
+        ret = []
+        if self._base:
+            ret.append("base=%r" % self._base)
+        if self._ori_diff:
+            ret.append("diff=%r" % self._ori_diff)
+        return ", ".join(ret)
+
     def _fix(self):
         return time.time() - self.diff
 
@@ -1002,6 +1164,9 @@ class DelayedEval(VolatileValue):
     def __init__(self, expr):
         self.expr = expr
 
+    def _command_args(self):
+        return "expr=%r" % self.expr
+
     def _fix(self):
         return eval(self.expr)
 
@@ -1012,6 +1177,16 @@ def __init__(self, start=0, step=1, restart=-1):
         self.step = step
         self.restart = restart
 
+    def _command_args(self):
+        ret = []
+        if self.start:
+            ret.append("start=%r" % self.start)
+        if self.step != 1:
+            ret.append("step=%r" % self.step)
+        if self.restart != -1:
+            ret.append("restart=%r" % self.restart)
+        return ", ".join(ret)
+
     def _fix(self):
         v = self.val
         if self.val == self.restart:
@@ -1027,6 +1202,15 @@ def __init__(self, s, p=0.01, n=None):
         self.p = p
         self.n = n
 
+    def _command_args(self):
+        ret = []
+        ret.append("s=%r" % self.s)
+        if self.p != 0.01:
+            ret.append("p=%r" % self.p)
+        if self.n:
+            ret.append("n=%r" % self.n)
+        return ", ".join(ret)
+
     def _fix(self):
         return corrupt_bytes(self.s, self.p, self.n)
 


Test patch:
diff --git a/test/regression.uts b/test/regression.uts
--- a/test/regression.uts
+++ b/test/regression.uts
@@ -10229,22 +10229,27 @@ random.seed(0x2807)
 r6 = RandIP6()
 assert(r6 == ("d279:1205:e445:5a9f:db28:efc9:afd7:f594" if six.PY2 else
               "240b:238f:b53f:b727:d0f9:bfc4:2007:e265"))
+assert(r6.command() == "RandIP6()")
 
 random.seed(0x2807)
-r6 = RandIP6("2001:db8::-") 
+r6 = RandIP6("2001:db8::-")
 assert(r6 == ("2001:0db8::e445" if six.PY2 else "2001:0db8::b53f"))
+assert(r6.command() == "RandIP6(ip6template='2001:db8::-')")
 
 r6 = RandIP6("2001:db8::*")
 assert(r6 == ("2001:0db8::efc9" if six.PY2 else "2001:0db8::bfc4"))
+assert(r6.command() == "RandIP6(ip6template='2001:db8::*')")
 
 = RandMAC
 
 random.seed(0x2807)
-rm = RandMAC() 
+rm = RandMAC()
 assert(rm == ("d2:12:e4:5a:db:ef" if six.PY2 else "24:23:b5:b7:d0:bf"))
+assert(rm.command() == "RandMAC()")
 
 rm = RandMAC("00:01:02:03:04:0-7")
 assert(rm == ("00:01:02:03:04:05" if six.PY2 else "00:01:02:03:04:01"))
+assert(rm.command() == "RandMAC(template='00:01:02:03:04:0-7')")
 
 
 = RandOID
@@ -10252,18 +10257,25 @@ assert(rm == ("00:01:02:03:04:05" if six.PY2 else "00:01:02:03:04:01"))
 random.seed(0x2807)
 ro = RandOID()
 assert(ro == "7.222.44.194.276.116.320.6.84.97.31.5.25.20.13.84.104.18")
+assert(ro.command() == "RandOID()")
 
 ro = RandOID("1.2.3.*")
 assert(ro == "1.2.3.41")
+assert(ro.command() == "RandOID(fmt='1.2.3.*')")
 
 ro = RandOID("1.2.3.0-28")
 assert(ro == ("1.2.3.11" if six.PY2 else "1.2.3.12"))
+assert(ro.command() == "RandOID(fmt='1.2.3.0-28')")
+
+ro = RandOID("1.2.3.0-28", depth=RandNumExpo(0.2), idnum=RandNumExpo(0.02))
+assert(ro.command() == "RandOID(fmt='1.2.3.0-28', depth=RandNumExpo(lambd=0.2), idnum=RandNumExpo(lambd=0.02))")
 
 = RandRegExp
 
 random.seed(0x2807)
 rex = RandRegExp("[g-v]* @? [0-9]{3} . (g|v)")
 bytes(rex) == ('vmuvr @ 906 \x9e g' if six.PY2 else b'irrtv @ 517 \xc2\xb8 v')
+assert(rex.command() == "RandRegExp(regexp='[g-v]* @? [0-9]{3} . (g|v)')")
 
 rex = RandRegExp("[:digit:][:space:][:word:]")
 assert re.match(b"\\d\\s\\w", bytes(rex))
@@ -10272,33 +10284,37 @@ assert re.match(b"\\d\\s\\w", bytes(rex))
 
 random.seed(0x2807)
 cb = CorruptedBytes("ABCDE", p=0.5)
+assert(cb.command() == "CorruptedBytes(s='ABCDE', p=0.5)")
 assert(sane(raw(cb)) in [".BCD)", "&BCDW"])
 
 cb = CorruptedBits("ABCDE", p=0.2)
+assert(cb.command() == "CorruptedBits(s='ABCDE', p=0.2)")
 assert(sane(raw(cb)) in ["ECk@Y", "QB.P."])
 
 = RandEnumKeys
 random.seed(0x2807)
 rek = RandEnumKeys({'a': 1, 'b': 2, 'c': 3}, seed=0x2807)
 rek.enum.sort()
+assert(rek.command() == "RandEnumKeys(enum=['a', 'b', 'c'], seed=10247)")
 r = str(rek)
-r
 assert(r == ('c' if six.PY2 else 'a'))
 
 = RandSingNum
 random.seed(0x2807)
-rs = RandSingNum(-28, 7)._fix()
-rs
-assert(rs in [2, 3])
+rs = RandSingNum(-28, 7)
+assert(rs._fix() in [2, 3])
+assert(rs.command() == "RandSingNum(mn=-28, mx=7)")
 
 = Rand*
 random.seed(0x2807)
 rss = RandSingString()
 assert(rss == ("CON:" if six.PY2 else "foo.exe:"))
+assert(rss.command() == "RandSingString()")
 
 random.seed(0x2807)
 rts = RandTermString(4, "scapy")
 assert(sane(raw(rts)) in ["...Zscapy", "$#..scapy"])
+assert(rts.command() == "RandTermString(size=4, term=%s'scapy')" % '' if six.PY2 else 'b')
 
 = RandInt (test __bool__)
 a = "True" if RandNum(False, True) else "False"
@@ -10307,22 +10323,30 @@ assert a in ["True", "False"]
 = Various volatiles
 
 random.seed(0x2807)
-assert RandNumGamma(1, 42)._fix() in (8, 73)
+rng = RandNumGamma(1, 42)
+assert rng._fix() in (8, 73)
+assert rng.command() == "RandNumGamma(alpha=1, beta=42)"
 
 random.seed(0x2807)
-assert RandNumGauss(1, 42) == 8
+rng = RandNumGauss(1, 42)
+assert rng._fix() == 8
+assert rng.command() == "RandNumGauss(mu=1, sigma=42)"
 
-print("RandEnum()", RandEnum(1, 42, seed=0x2807)._fix())
-assert RandEnum(1, 42, seed=0x2807) == (13 if six.PY2 else 37)
+renum = RandEnum(1, 42, seed=0x2807)
+assert renum == (13 if six.PY2 else 37)
+assert renum.command() == "RandEnum(min=1, max=42, seed=10247)"
 
-print("RandPool()", RandPool((IncrementalValue(), 42), (IncrementalValue(), 0))._fix())
-assert RandPool((IncrementalValue(), 42), (IncrementalValue(), 0)) == 0
+rp = RandPool((IncrementalValue(), 42), (IncrementalValue(), 0))
+assert rp == 0
+assert rp.command() == "RandPool((IncrementalValue(), 42), (IncrementalValue(), 0))"
 
-print("DelayedEval()", DelayedEval("3 + 1")._fix())
-assert DelayedEval("3 + 1") == 4
+de = DelayedEval("3 + 1")
+assert de == 4
+assert de.command() == "DelayedEval(expr='3 + 1')"
 
 v = IncrementalValue(restart=2)
 assert v == 0 and v == 1 and v == 2 and v == 0
+assert v.command() == "IncrementalValue(restart=2)"
 
 
 ############
@@ -11583,8 +11607,9 @@ RANDUUID_FIXED = uuid.uuid4()
 
 = RandUUID default behaviour
 
-u = RandUUID()._fix()
-assert u.version == 4
+ru = RandUUID()
+assert ru._fix().version == 4
+assert ru.command() == "RandUUID()"
 
 = RandUUID incorrect implicit args
 
@@ -11626,7 +11651,9 @@ u = RandUUID(version=1, clock_seq=0x1234)._fix()
 assert u.version == 1
 assert u.clock_seq == 0x1234
 
-u = RandUUID(version=1, node=0x1234, clock_seq=0x1bcd)._fix()
+ru = RandUUID(version=1, node=0x1234, clock_seq=0x1bcd)
+assert ru.command() == "RandUUID(node=4660, clock_seq=7117, version=1)"
+u = ru._fix()
 assert u.version == 1
 assert u.node == 0x1234
 assert u.clock_seq == 0x1bcd
@@ -11654,8 +11681,10 @@ assert expect_exception(ValueError, lambda: RandUUID(version=1, name="scapy"))
 
 = RandUUID v5 UUID
 
-u = RandUUID(version=5, namespace=RANDUUID_FIXED, name="scapy")._fix()
+ru = RandUUID(version=5, namespace=RANDUUID_FIXED, name="scapy")
+u = ru._fix()
 assert u.version == 5
+assert ru.command() == "RandUUID(namespace=%r, name='scapy', version=5)" % RANDUUID_FIXED
 
 u2 = RandUUID(version=5, namespace=RANDUUID_FIXED, name="scapy")._fix()
 assert u2.version == 5
@@ -11695,7 +11724,9 @@ assert re.match(r'[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}', str(RandUUID()), r
 
 = RandUUID with a static part
 * RandUUID template can contain static part such a 01234567-89ab-*-01*-*****ef
-assert re.match(r'01234567-89ab-[0-9a-f]{4}-01[0-9a-f]{2}-[0-9a-f]{10}ef', str(RandUUID('01234567-89ab-*-01*-*****ef')), re.I) is not None
+ru = RandUUID('01234567-89ab-*-01*-*****ef')
+assert re.match(r'01234567-89ab-[0-9a-f]{4}-01[0-9a-f]{2}-[0-9a-f]{10}ef', str(ru), re.I) is not None
+assert ru.command() == "RandUUID(template='01234567-89ab-*-01*-*****ef')"
 
 = RandUUID with a range part
 * RandUUID template can contain a part with a range of values such a 01234567-89ab-*-01*-****c0:c9ef