docs: def p0f_impersonate(pkt, osgenre=None, osdetails=None, signature=None,
                      extrahops=0, mtu=1500, uptime=None):
      """Modifies pkt so that p0f will think it has been sent by a
  specific OS.  If osdetails is None, then we randomly pick up a
  personality matching osgenre. If osgenre and signature are also None,
  we use a local signature (using p0f_getlocalsigs). If signature is
  specified (as a tuple), we use the signature.
  
  For now, only TCP Syn packets are supported.
  Some specifications of the p0f.fp file are not (yet) implemented."""
      pkt = pkt.copy()
      # pkt = pkt.__class__(raw(pkt))
      while pkt.haslayer(IP) and pkt.haslayer(TCP):
          pkt = pkt.getlayer(IP)
          if isinstance(pkt.payload, TCP):
              break
          pkt = pkt.payload
  
      if not isinstance(pkt, IP) or not isinstance(pkt.payload, TCP):
          raise TypeError("Not a TCP/IP packet")
  
      db = p0f_selectdb(pkt.payload.flags)
      if osgenre:
          pb = db.get_base()
          if pb is None:
              pb = []
          pb = [x for x in pb if x[6] == osgenre]
          if osdetails:
              pb = [x for x in pb if x[7] == osdetails]
      elif signature:
          pb = [signature]
      else:
          pb = p0f_getlocalsigs()[db]
      if db == p0fr_kdb:
          # 'K' quirk <=> RST+ACK
          if pkt.payload.flags & 0x4 == 0x4:
              pb = [x for x in pb if 'K' in x[5]]
          else:
              pb = [x for x in pb if 'K' not in x[5]]
      if not pb:
          raise Scapy_Exception("No match in the p0f database")
      pers = pb[random.randint(0, len(pb) - 1)]
  
      # options (we start with options because of MSS)
      # Take the options already set as "hints" to use in the new packet if we
      # can. MSS, WScale and Timestamp can all be wildcarded in a signature, so
      # we'll use the already-set values if they're valid integers.
      orig_opts = dict(pkt.payload.options)
      int_only = lambda val: val if isinstance(val, six.integer_types) else None
      mss_hint = int_only(orig_opts.get('MSS'))
      wscale_hint = int_only(orig_opts.get('WScale'))
      ts_hint = [int_only(o) for o in orig_opts.get('Timestamp', (None, None))]
  
      options = []
      if pers[4] != '.':
          for opt in pers[4].split(','):
              if opt[0] == 'M':
                  # MSS might have a maximum size because of window size
                  # specification
                  if pers[0][0] == 'S':
                      maxmss = (2**16 - 1) // int(pers[0][1:])
                  else:
                      maxmss = (2**16 - 1)
                  # disregard hint if out of range
                  if mss_hint and not 0 <= mss_hint <= maxmss:
                      mss_hint = None
                  # If we have to randomly pick up a value, we cannot use
                  # scapy RandXXX() functions, because the value has to be
                  # set in case we need it for the window size value. That's
                  # why we use random.randint()
                  if opt[1:] == '*':
                      if mss_hint is not None:
                          options.append(('MSS', mss_hint))
                      else:
                          options.append(('MSS', random.randint(1, maxmss)))
                  elif opt[1] == '%':
                      coef = int(opt[2:])
                      if mss_hint is not None and mss_hint % coef == 0:
                          options.append(('MSS', mss_hint))
                      else:
                          options.append((
                              'MSS', coef * random.randint(1, maxmss // coef)))
                  else:
                      options.append(('MSS', int(opt[1:])))
              elif opt[0] == 'W':
                  if wscale_hint and not 0 <= wscale_hint < 2**8:
                      wscale_hint = None
                  if opt[1:] == '*':
                      if wscale_hint is not None:
                          options.append(('WScale', wscale_hint))
                      else:
                          options.append(('WScale', RandByte()))
                  elif opt[1] == '%':
                      coef = int(opt[2:])
                      if wscale_hint is not None and wscale_hint % coef == 0:
                          options.append(('WScale', wscale_hint))
                      else:
                          options.append((
                              'WScale', coef * RandNum(min=1, max=(2**8 - 1) // coef)))  # noqa: E501
                  else:
                      options.append(('WScale', int(opt[1:])))
              elif opt == 'T0':
                  options.append(('Timestamp', (0, 0)))
              elif opt == 'T':
                  # Determine first timestamp.
                  if uptime is not None:
                      ts_a = uptime
                  elif ts_hint[0] and 0 < ts_hint[0] < 2**32:
                      # Note: if first ts is 0, p0f registers it as "T0" not "T",
                      # hence we don't want to use the hint if it was 0.
                      ts_a = ts_hint[0]
                  else:
                      ts_a = random.randint(120, 100 * 60 * 60 * 24 * 365)
                  # Determine second timestamp.
                  if 'T' not in pers[5]:
                      ts_b = 0
                  elif ts_hint[1] and 0 < ts_hint[1] < 2**32:
                      ts_b = ts_hint[1]
                  else:
                      # FIXME: RandInt() here does not work (bug (?) in
                      # TCPOptionsField.m2i often raises "OverflowError:
                      # long int too large to convert to int" in:
                      #    oval = struct.pack(ofmt, *oval)"
                      # Actually, this is enough to often raise the error:
                      #    struct.pack('I', RandInt())
                      ts_b = random.randint(1, 2**32 - 1)
                  options.append(('Timestamp', (ts_a, ts_b)))
              elif opt == 'S':
                  options.append(('SAckOK', ''))
              elif opt == 'N':
                  options.append(('NOP', None))
              elif opt == 'E':
                  options.append(('EOL', None))
              elif opt[0] == '?':
                  if int(opt[1:]) in TCPOptions[0]:
                      optname = TCPOptions[0][int(opt[1:])][0]
                      optstruct = TCPOptions[0][int(opt[1:])][1]
                      options.append((optname,
                                      struct.unpack(optstruct,
                                                    RandString(struct.calcsize(optstruct))._fix())))  # noqa: E501
                  else:
                      options.append((int(opt[1:]), ''))
              # FIXME: qqP not handled
              else:
                  warning("unhandled TCP option " + opt)
              pkt.payload.options = options
  
      # window size
      if pers[0] == '*':
          pkt.payload.window = RandShort()
      elif pers[0].isdigit():
          pkt.payload.window = int(pers[0])
      elif pers[0][0] == '%':
          coef = int(pers[0][1:])
          pkt.payload.window = coef * RandNum(min=1, max=(2**16 - 1) // coef)
      elif pers[0][0] == 'T':
          pkt.payload.window = mtu * int(pers[0][1:])
      elif pers[0][0] == 'S':
          # needs MSS set
          mss = [x for x in options if x[0] == 'MSS']
          if not mss:
              raise Scapy_Exception("TCP window value requires MSS, and MSS option not set")  # noqa: E501
          pkt.payload.window = mss[0][1] * int(pers[0][1:])
      else:
          raise Scapy_Exception('Unhandled window size specification')
  
      # ttl
      pkt.ttl = pers[1] - extrahops
      # DF flag
      pkt.flags |= (2 * pers[2])
      # FIXME: ss (packet size) not handled (how ? may be with D quirk
      # if present)
      # Quirks
      if pers[5] != '.':
          for qq in pers[5]:
              # FIXME: not handled: P, I, X, !
              # T handled with the Timestamp option
              if qq == 'Z':
                  pkt.id = 0
              elif qq == 'U':
                  pkt.payload.urgptr = RandShort()
              elif qq == 'A':
                  pkt.payload.ack = RandInt()
              elif qq == 'F':
                  if db == p0fo_kdb:
                      pkt.payload.flags |= 0x20  # U
                  else:
                      pkt.payload.flags |= random.choice([8, 32, 40])  # P/U/PU
              elif qq == 'D' and db != p0fo_kdb:
                  pkt /= conf.raw_layer(load=RandString(random.randint(1, 10)))  # XXX p0fo.fp  # noqa: E501
              elif qq == 'Q':
                  pkt.payload.seq = pkt.payload.ack
              # elif qq == '0': pkt.payload.seq = 0
          # if db == p0fr_kdb:
          # '0' quirk is actually not only for p0fr.fp (see
          # packet2p0f())
      if '0' in pers[5]:
          pkt.payload.seq = 0
      elif pkt.payload.seq == 0:
          pkt.payload.seq = RandInt()
  
      while pkt.underlayer:
          pkt = pkt.underlayer
      return pkt