docs: def parse(self, ticket):
          """Parses the passed ticket, returning a tuple containing the digest,
          user_id, valid_until, tokens, and user_data fields
          """
          if len(ticket) < self._min_ticket_size():
              raise TicketParseError(ticket, 'Invalid ticket length')
  
          digest_len = self._hash.digest_size * 2
          digest = ticket[:digest_len]
  
          try:
              time_len = 8
              time = int(ticket[digest_len:digest_len + time_len], 16)
          except:
              raise TicketParseError(ticket, 'Invalid time field')
  
          parts = ticket[digest_len + time_len:].split('!')
          if len(parts) != 3:
              raise TicketParseError(ticket, 'Missing parts')
  
          user_id = ulp.unquote(parts[0])
          tokens = ()
          if parts[1]:
              tokens = tuple((ulp.unquote(t) for t in parts[1].split(',')))
  
          user_data = ulp.unquote(parts[2])
  
          return TicketInfo(digest, user_id, tokens, user_data, time)