#!/usr/bin/python

from subprocess import call
from optparse import OptionParser
from random import uniform, seed
import operator, os, resource, sys

def excluded(loc, *ranges) :
    for r in ranges :
        if loc >= r[0] and loc <= r[1] : return True
    return False

def valpos(fontlen, opts) :
    if opts.input :
        gen = []
        ifh = file(opts.input)
        for l in ifh.readlines() :
            f = l.split(',')
            gen.append((int(f[1], 0), int(f[2], 0)))
        ifh.close()
        def dogen() :
            for g in gen :
                yield (g)
        return dogen
    elif opts.linear :
        def gen() :
            while (1) :
                for c in xrange(int(opts.position,0), fontlen) :
                    yield (c, int(uniform(0, 256)))
                opts.position = 0;
        return gen
    elif opts.position :
        if opts.value :
            def gen() :
                yield (int(opts.position, 0), int(opts.value, 0))
            return gen
        else :
            def gen() :
                while (1) :
                    yield(int(opts.position, 0), int(uniform(0, 256)))
            return gen
    elif opts.value :
        def gen() :
            while (1) :
                for c in xrange(0, fontlen) :
                    if opts.linear :
                        yield (c, int(opts.value, 0))
                    else :
                        yield(int(uniform(0, fontlen)), int(opts.value, 0))
        return gen
    else :
        def gen() :
            while (1) :
                yield(int(uniform(0, fontlen)), int(uniform(0, 256)))
        return gen

def num(d): 
	return reduce(lambda n, b: n << 8 | ord(b), d, 0)

def tag(d):
	s = ''
	while d != 0:
		s = chr(d & 0xff)+s
		d >>= 8
	return s

def table_from_offset(o,tables):
	for (t,s,e) in tables:
		if o >= s and o < e: return (t,s,e)
	return None

parser = OptionParser(usage="usage: %prog -f font [options] -- command")
parser.add_option("-l", "--logfile", help="Log results to this file")
parser.add_option("-f", "--font", help="Required font file to corrupt")
parser.add_option("-p", "--position", default="0", help="Specifies position to corrupt (hex)")
parser.add_option("-v", "--value", help="Specifies value to use (dec)")
parser.add_option("-i", "--input", help=".log file to read test values from")
parser.add_option("-V", "--verbose", action="store_true", help="Be noisy")
parser.add_option("-t", "--timeout", type="int", help="limit subprocess time in seconds")
parser.add_option("--memory", type="int", help="limit subprocess address space in MB")
parser.add_option("-c","--count",type='int',help='Flag every count iterations')
parser.add_option("-r","--random",help="Seed the random number generator with the given string")
parser.add_option("-L","--linear",action="store_true",help="Linearly process every location in the source font")
parser.add_option("--valgrind",action="store_true",help="Run tests with valgrind and report errors")

(opts, args) = parser.parse_args()

if opts.random : seed(opts.random)
if opts.valgrind : 
    args.insert(0, "-q")
    args.insert(0, "valgrind")

fontlen = os.path.getsize(opts.font)
if opts.logfile :
    log = open(opts.logfile, "a")
else :
    log = sys.stdout

def rlimit() :
    if opts.timeout :
        resource.setrlimit(resource.RLIMIT_CPU, (opts.timeout, opts.timeout))
    if opts.memory :
        mem = opts.memory * 1024 * 1024
        resource.setrlimit(resource.RLIMIT_AS, (mem, mem))

def read_tabledir(font):
	def read_entry(): 
		t = num(font.read(4)); font.seek(4, 1); o = num(font.read(4)); l = num(font.read(4))
		return (t,o,o+l)
	
	font.seek(4,0)
	num_tables = num(font.read(2))
	font.seek(2*3,1)
	
	return [(0,0,4+4*2 + 4*4*num_tables)] + [read_entry() for i in range(num_tables)]

font = open(opts.font, "r+b")
tables = read_tabledir(font)
tables.sort(key=operator.itemgetter(1))
count = 0

for loc, val in valpos(fontlen, opts)() :
    font.seek(loc, 0)
    oldval = font.read(1)
    font.seek(loc, 0)
    while val == oldval : val = int(uniform(0, 256))
    font.write(chr(val))
    font.close()

    if opts.verbose :
        print "0x%X,%d" % (loc, val)
    count += 1
    if opts.count and count % opts.count == 0 :
        print count / opts.count,
        sys.stdout.flush()
    subfile = open("templog.txt", "w")
    errfile = open("errlog.txt", "w")
    retval = call(args, stdout=subfile, stderr=errfile, preexec_fn=rlimit, close_fds = True)
    subfile.close()
    errfile.close()
    lenerr = os.stat("errlog.txt").st_size
    if retval < 0 :
    	(t,s,_) = table_from_offset(loc, tables) or (0,0,fontlen)
        print >>log, "%d,0x%x,%d,%s0x%x" % (retval, loc, val, tag(t) + '+' if t else '', loc-s)
    elif opts.verbose or lenerr :
        print >>log, ",0x%x,%d" % (loc, val)
        if lenerr :
            errfile = open("errlog.txt", "r")
            for l in errfile.readlines() : print >>log, l.rstrip()
            errfile.close()
    log.flush()
    font = open(opts.font, "r+b")
    font.seek(loc, 0)
    font.write(oldval)

if font != None :
    font.close()

