Skip to content

Instantly share code, notes, and snippets.

@DanaEpp
Created October 20, 2022 17:56

Revisions

  1. DanaEpp created this gist Oct 20, 2022.
    109 changes: 109 additions & 0 deletions guid_reaper.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,109 @@
    #!/bin/env python3

    import argparse
    import datetime
    import re
    import sys
    import uuid

    ###############################################################################
    # Based off of Daniel Thatcher's guid tool
    # Additional reading from https://duo.com/labs/tech-notes/breaking-down-uuids
    ###############################################################################

    # A nano second is a billionth of a second, so...
    # 1 second = 1e7 100-nanosecond intervals
    NANO_INTERVAL = 1e7

    def uuid_time(uid):
    # Gregorian reform to the Christian calendar (Oct 15, 1582)
    # See https://datatracker.ietf.org/doc/html/rfc4122#section-4.2.2
    dt_base = datetime.datetime( 1582, 10, 15 )
    return dt_base + datetime.timedelta(microseconds=uid.time//10)

    def uuid_mac(uid):
    return ":".join(re.findall('..', '%012x' % uid.node))

    def dump_guid(guid):
    try:
    uid = uuid.UUID(guid)
    except ValueError:
    print("Invalid GUID")
    sys.exit(2)

    print ("GUID version: {}".format(uid.version))

    if uid.version == 1:
    t = uuid_time(uid)
    print("Time: {}".format(t))
    print("Timestamp: {}".format(uid.time))
    print("Node: {}".format(uid.node))
    m = uuid_mac(uid)
    print("MAC address: {}".format(m))
    print("Clock sequence: {}".format(uid.clock_seq))

    def uuid1(node, clock_seq, timestamp):
    time_low = timestamp & 0xffffffff
    time_mid = (timestamp >> 32) & 0xffff
    time_hi_version = (timestamp >> 48) & 0x0fff
    clock_seq_low = clock_seq & 0xff
    clock_seq_hi_variant = (clock_seq >> 8) & 0x3f
    return uuid.UUID(fields=(time_low, time_mid, time_hi_version,
    clock_seq_hi_variant, clock_seq_low, node), version=1)

    def get_precision(timestamp):
    # Determine the precision by looking at how many 0 are at the end
    # of the previously captured timestamp
    ts = str(timestamp)
    l = len(ts) - len(ts.rstrip('0'))
    return int("1".ljust(l+1, '0'))

    def gen_guids(sample_guid, estimated_ts):
    uid = uuid.UUID(sample_guid)
    if uid.version != 1:
    print( "We can only generate GUIDs v1. Aborting." )
    sys.exit(2)

    # Calculate the timestamp for the first GUID
    dt_base = datetime.datetime( 1582, 10, 15 )
    base_guid_time = estimated_ts - dt_base
    base_timestamp = int(base_guid_time.total_seconds() * NANO_INTERVAL)

    seconds = 2
    precision = get_precision(uid.time)
    start_time = int(base_timestamp - (NANO_INTERVAL) * seconds)
    end_time = int(base_timestamp + (NANO_INTERVAL) * seconds)

    for t in range( start_time, end_time, precision ):
    yield uuid1(uid.node, uid.clock_seq, t)

    def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("-d", "--dump", help="Dump decoded GUID and exit", action="store_true")
    parser.add_argument("-t", "--time",
    help="The estimated time at which the GUID was generated. ie: '2021-02-27 17:42:01'",
    type=lambda s: datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S"))
    parser.add_argument("guid", help="The GUID to inspect")
    args = parser.parse_args()

    # Validate GUID
    try:
    _ = uuid.UUID(args.guid)
    except:
    print("Invalid GUID. Aborting.")
    sys.exit(1)

    # Dump GUID and exit if that's what we want
    if args.dump:
    dump_guid(args.guid)
    sys.exit(0)

    if args.time is None:
    print( "Timestamp required. Use '-t' option. Aborting.")
    sys.exit(1)

    for u in gen_guids(args.guid, args.time):
    print(u)

    if __name__ == "__main__":
    main()