#!/usr/bin/python
#
# Generate Multi-party NSL protocol description for n parties
#
# Input: P variant
#
# variant uses some bits:
#    bit    mask    meaning if set to '1'
#            (message type 1)
#     0    1    nonces in reverse 
#     1    2    nonces after agents
#     2    4    agents in reverse 
#     3    8    interleaved variant
#             (message type 2)
#     4    16    nonces in reverse in message 2
#
# Convention similar to e.g. Prolog: capitals indicate open variables;
# in particular, they can be bound by _any_ value during the run,
# assuming full type flaws.
#
import sys
from optparse import OptionParser

def parseArgs():
    usage = "usage: %s [opts] Parties Variant" % sys.argv[0]
    parser = OptionParser(usage=usage)
    parser.add_option('-p','--protocol', dest='protocol',
        help='Generate another protocol [nsl,bke]', default="nsl",
        action='store')
    (opts, args) = parser.parse_args()
    if len(args) != 2:
        parser.print_help()
        sys.exit(0)
    if opts.protocol not in ["nsl","bke","nsl-priv-noprop","nsl-pub-nap","bke-nap"]:
        print "I don't know the %s protocol." % (opts.protocol)
        sys.exit(0)
    return (opts,args)


def variablerole (r, inrole):
    if r == inrole or inrole == 0:
        return False
    else:
        return True

def role (r,inrole):
    global P

    return "r%i" % (r % P)

def zeroconst ():
    
    """ This is 0 or some other stupid constant """

    return "zeroconst"

def nonce (r,inrole):
    global P

    if r == inrole:
        # nonce of our own
        return "n%i" % (r % P)
    else:
        # a variable: we want to see this in the notation
        return "N%i" % (r % P)

def extend (s1, s2):
    if s1 == "":
        return s2
    else:
        return s1 + "," + s2

def weavel (l1,l2,reverse1,swap,reverse2,interleave):
    """ l1 is typically a list of nonces, l2 might be empty (names) """
    global variant

    if reverse1:
        l1.reverse()
    if l2 == []:
        return l1
    else:
        if reverse2:
            l2.reverse()
        if swap:
            # swap
            l3 = l1
            l1 = l2
            l2 = l3
        if interleave:
            rl = []
            largest = max(len(l1),len(l2))
            for i in range (0,largest):
                if i < len(l1):
                    rl.append(l1[i])
                if i < len(l2):
                    rl.append(l2[i])
            return rl
        else:
            return l1 + l2

def message1 (label,inrole):
    global P,variant,opts

    if opts.protocol in ['bke','nsl']:
        noncelist = []
        for i in range(0,label+1):
            noncelist.append(nonce(i,inrole))
        rolelist = []
        for i in range(0,P):
            if i != (label+1) % P:
                rolelist.append(role(i,inrole))

        return ",".join(weavel(noncelist,rolelist,
            (variant & 1 != 0),
            (variant & 2 != 0),
            (variant & 4 != 0),
            (variant & 8 != 0)
            ))
    elif opts.protocol == 'nsl-priv-noprop':

        list = []
        for i in range(0,P):
            list.append(role(i,inrole))
        list.append(nonce(0,inrole))
        msg = ",".join(list)

        for i in range(1,label+1):
            msg = "{ %s,%s }sk(%s)" % (msg,nonce(i,inrole),role(i,inrole))

        return msg

    elif opts.protocol == 'nsl-pub-nap':

        list = []
        for i in range(0,P):
            list.append(role(i,inrole))
        list.append(nonce(0,inrole))
        msg = ",".join(list)

        for i in range(1,label+1):
            msg = "{ %s }sk(%s), %s" % (msg,role(i,inrole),nonce(i,inrole))

        msg = "{ %s }pk(%s)" % (msg,role(label+1,inrole))

        return msg
    elif opts.protocol == 'bke-nap':

        list = []
        for i in range(0,P):
            list.append(role(i,inrole))
        list.append(nonce(0,inrole))
        msg = ",".join(list)

        for i in range(1,label+1):
            msg = "{ %s }sk(%s), %s" % (msg,role(i,inrole),nonce(i,inrole))

        msg = "{ %s }pk(%s)" % (msg,role(label+1,inrole))

        return msg
    else:
        print "Hmm, I don't know how to create the first message for protocol %s" % (opts.protocol)

def message2 (label,inrole):
    global P,variant,opts

    if opts.protocol == "nsl":
        noncelist = []
        for i in range (((label + 1) % P),P):
            noncelist.append(nonce(i,inrole))

        return ",".join(weavel(noncelist,[],
            (variant & 16 != 0),
            False,
            False,
            False
            ))
    elif opts.protocol == "bke":
        noncelist = []
        for i in range (((label + 1) % P) + 1,P):
            noncelist.append(nonce(i,inrole))
        if len(noncelist) == 0:
            noncelist.append(zeroconst())

        return ",".join(weavel(noncelist,[],
            (variant & 16 != 0),
            False,
            False,
            False
            ))
    elif opts.protocol in ['nsl-priv-noprop','nsl-pub-nap']:
        msg = message1(P-1,inrole)
        for i in range(0,label-P+1):
            msg = "{ %s }sk(%s)" % (msg,role(i,inrole))

        if opts.protocol == 'nsl-pub-nap':
            msg = "{ %s }pk(%s)" % (msg,role(label+1,inrole))

        return msg
    elif opts.protocol == 'bke-nap':
        msg = message1(P-1,inrole)
        for i in range(0,label-P+1):
            msg = "{ %s }sk(%s)" % (msg,role(i,inrole))

        msg = "{ %s }%s" % (msg,nonce((label+1) % P,inrole))

        return msg
    else:
        print "Hmm, I don't know how to create the final message for protocol %s" % (opts.protocol)

def message (label,inrole):
    global P,opts

    if opts.protocol in ['bke','nsl']:
        s = "{ "
        if label < P:
            s = s + message1 (label,inrole)
        else:
            s = s + message2 (label,inrole)

        if opts.protocol == "bke" and not (label < P):
            s = s + " }" + nonce((label+1) % P, inrole)
        else:
            s = s + " }pk(%s)" % role(label+1,inrole)
        return s
    else:
        if label < P:
            return message1 (label,inrole)
        else:
            return message2 (label,inrole)


def action (event,label,inrole):
    s = "\t\t%s_%i(%s,%s, " % (event,label, role(label,inrole),
            role(label+1,inrole))
    s += message (label,inrole)
    s += " );\n"
    return s

def read (label,inrole):
    return action ("read", label,inrole)


def send (label,inrole):
    return action ("send", label,inrole)

def roledef (r):
    global P,opts

    s = ""
    s += "\trole " + role(r,r) + "\n\t{\n"

    # constants for this role
    
    s += "\t\tconst " + nonce (r,r) + ": Nonce;\n"

    # variables
    
    s += "\t\tvar "
    nr = 0
    for i in range (0,P):
        if r != i:
            if nr > 0:
                s += ","
            s += nonce(i,r)
            nr += 1

    s += ": Nonce;\n"

    # implicit role variables
    
    rolevars = []
    for i in range (0,P):
        if variablerole(i,r):
            rolevars.append(role(i,r))

    if rolevars != []:
        s += "\t\t// Implicit role variables: "
        s += ",".join(rolevars)
        s += ": Role;\n"
        
    # actions
    
    s += "\n"
    if r > 0:
        # Initial read
        s += read(r-1,r)
    s += send(r,r)
    s += read(P+r-1,r)
    if r < (P-1):
        # Final send
        s += send(P+r,r)
    
    # claims
    
    if opts.protocol in ['bke','nsl','nsl-pub-nap','bke-nap']:
        s += "\t\tclaim_%sa( %s, Secret, %s );\n" % (role(r,r), role(r,r),
                nonce(r,r))
    s += "\t\tclaim_%sb( %s, Nisynch );\n" % (role(r,r), role(r,r))

    # close
    s += "\t}\n\n"
    return s


def protocol (args):
    global P,variant,opts

    P = int(args[0])
    variant = int(args[1])

    s = ""
    s += "// Generalized %s protocol for %i parties\n\n" % (opts.protocol,P)
    s += "// " + str(opts) + "\n\n"
    s += "// Variant %i\n" % variant
    s += "const pk: Function;\n"
    s += "secret sk: Function;\n"
    s += "inversekeys (pk,sk);\n"
    
    if opts.protocol == "bke":
        s += "usertype Globalconstant;\n"
        s += "const %s: Globalconstant;\n" % (zeroconst())

    s += "\n"

    s += "protocol mnsl%iv%i(" % (P,variant)
    for i in range (0,P):
        if i > 0:
            s += ","
        s += role(i,i)
    s += ")\n{\n"

    for i in range (0,P):
        s += roledef(i)
    
    s += "}\n\n"

    s += "const Alice, Bob: Agent;\n\n"

    s += "const Eve: Agent;\n"
    s += "untrusted Eve;\n"
    s += "const ne: Nonce;\n"
    s += "compromised sk(Eve);\n"

    s += "\n"
    return s

def main():
    global opts

    (opts,args) = parseArgs()
    print protocol(args)

# Only if main stuff
if __name__ == '__main__':
     main()
