<commit_before>// Copyright 2010 The GoGo Authors. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package main

import "./libgogo/_obj/libgogo"

type LineDesc struct {
    Line string;
    Offset uint64;

    NeedsFix uint64;
    PackageName string;
    FunctionName string;
};

type ParamDesc struct {
    Name string;
    TypePackage string;
    TypeName string;
    Ptr uint64;
};

//
// Function fetches the next line from the assembly file.
//
func GetLine(ld *LineDesc) {
    var line string;
    var i uint64 =0;
    var singleChar byte;
    var cmp uint64;

    for singleChar = GetCharWrapped(); (i<10) && (singleChar!=10) && (singleChar!=0); singleChar = GetCharWrapped() {
        libgogo.CharAppend(&line, singleChar);
        i = i +1;
    }
    cmp = libgogo.StringCompare(line, "  //--- ##");
    if cmp == 0 {
        singleChar = GetCharWrapped(); // should be number; the case of fixing
        ld.NeedsFix = libgogo.ToIntFromByte(singleChar);
        ld.NeedsFix = ld.NeedsFix - 48;
        ld.PackageName = "";
        ld.FunctionName = "";

        GetCharWrapped(); // Abolish '#'
        GetCharWrapped(); // Abolish '#'

        for singleChar = GetCharWrapped(); singleChar != 194 ; singleChar = GetCharWrapped() {
            libgogo.CharAppend(&ld.PackageName, singleChar);
        }
        GetCharWrapped(); // Abolish second part of dot (183)
        for singleChar = GetCharWrapped(); (singleChar != '#'); singleChar = GetCharWrapped() {
            libgogo.CharAppend(&ld.FunctionName, singleChar);
        }

        if (singleChar != 10) && (singleChar != 0) {
            for ; (singleChar != 0) && (singleChar != 10); singleChar = GetCharWrapped() {
                libgogo.CharAppend(&line, singleChar);
            }
        }
    } else {
        for ; (singleChar != 0) && (singleChar != 10); singleChar = GetCharWrapped() {
            libgogo.CharAppend(&line, singleChar);
        }
    }

    if singleChar == 0 {
        tok.id = TOKEN_EOS;
    }
    ld.Line = line;
    ld.Offset = 0;

}

func GetNextSymToken(ld *LineDesc) string {
    var symtoken string = "";
    var i uint64;
    var len uint64;
    len = libgogo.StringLength(ld.Line);
    for i = ld.Offset; (i < len) && (ld.Line[i] != ','); i=i+1 {
        if (ld.Line[i] != '/') {
            libgogo.CharAppend(&symtoken, ld.Line[i]);
        }
    }
    ld.Offset = i+1;
    return symtoken;
}

func ParseSymTblLine(ld *LineDesc) {
    var symtype string;
    var strCmp uint64;

    symtype = GetNextSymToken(ld);
    strCmp = libgogo.StringCompare(symtype, "TYPE");
    if strCmp == 0 {
        ParseSymTblType(ld);
    }
    strCmp = libgogo.StringCompare(symtype, "FUNC");
    if strCmp == 0 {
        ParseSymTblFunc(ld);
    }
}

func IsDefaultType(pkgType string) uint64 {
    var strCmp uint64;
    var retValue uint64 = 0;
    strCmp = libgogo.StringCompare(pkgType, "·uint64");
    if strCmp == 0 {
        retValue = 1;
    }
    strCmp = libgogo.StringCompare(pkgType, "·byte");
    if strCmp == 0 {
        retValue = 1;
    }
    strCmp = libgogo.StringCompare(pkgType, "·string");
    if strCmp == 0 {
        retValue = 1;
    }
    strCmp = libgogo.StringCompare(pkgType, "·bool");
    if strCmp == 0 {
        retValue = 1;
    }
    return retValue;
}

func GetPackageName(pkgType string) string {
    var retStr string;
    var i uint64;
    for i = 0; pkgType[i] != 194 ; i=i+1 {
        libgogo.CharAppend(&retStr, pkgType[i]);
    }
    return retStr;
}

func GetFuncName(pkgType string) string {
    var retStr string;
    var i uint64;
    var strLen uint64;
    strLen = libgogo.StringLength(pkgType);
    for i = 0; pkgType[i] != 183; i = i +1 {
    }
    i = i+1;
    for ; i < strLen; i = i +1 {
        libgogo.CharAppend(&retStr, pkgType[i]);
    }
    return retStr;
}

func ParseSymbolParam(pd *ParamDesc, paramType string) {
    var i uint64 = 0;
    var strLen uint64;

    pd.Name = "";
    pd.TypePackage = "";
    pd.TypeName = "";
    pd.Ptr = 0;

    strLen = libgogo.StringLength(paramType);
    if strLen > 0 {
        for i = 0; paramType[i] != ':' ; i=i+1 {
            libgogo.CharAppend(&pd.Name, paramType[i]);
        }
        i=i+1;
        if paramType[i] == '*' {
            pd.Ptr = 1;
            i=i+1;
        }
        for ;paramType[i] != 194; i=i+1 {
            libgogo.CharAppend(&pd.TypePackage, paramType[i]);
        }
        for i=i+2;i < strLen; i=i+1 {
            libgogo.CharAppend(&pd.TypeName, paramType[i]);
        }
    }
}

//
// Function parsing the main part of a function in a symbol table and adding it
// to the global symbol table.
//
func ParseSymTblFunc(ld *LineDesc) {
    var pd ParamDesc;
    var func_t *libgogo.TypeDesc = nil;
    var some_t *libgogo.TypeDesc = nil;
    var fwdStr string;
    var fwdNum uint64;
    var pkgFunc string;
    var funcName string;
    var pkgName string;
    var ind uint64;
    var paramType string;
    var tmpParam *libgogo.ObjectDesc;

    fwdStr = GetNextSymToken(ld);
    fwdNum = libgogo.StringToInt(fwdStr);
    pkgFunc = GetNextSymToken(ld);
    ind = libgogo.StringCompare(pkgFunc, "main·init");
    if ind != 0 {
        pkgName = GetPackageName(pkgFunc);
        funcName = GetFuncName(pkgFunc);
        func_t = NewFunction(funcName, pkgName, fwdNum);
        paramType = GetNextSymToken(ld);
        ind = libgogo.StringLength(paramType);
        for ; ind != 0 ; {
            ParseSymbolParam(&pd, paramType);
            tmpParam = libgogo.NewObject(pd.Name, "", libgogo.CLASS_PARAMETER);
            some_t = libgogo.GetType(pd.TypeName, pd.TypePackage, GlobalTypes, 1);
            if some_t != nil {
                tmpParam.ObjType = some_t;
                tmpParam.PtrType = pd.Ptr;
                libgogo.AddParameters(tmpParam, func_t);
            } else {
                LinkError("unable to find type '",pd.TypePackage,"·",pd.TypeName,"'.");
            }
            paramType = GetNextSymToken(ld);
            ind = libgogo.StringLength(paramType);
        }
    }

}

//
// Function parsing the main part of a type in a symbol table and adding it
// to the global symbol table.
//
func ParseSymTblType(ld *LineDesc) {
    var some_t *libgogo.TypeDesc = nil;
    var fwdStr string;
    var fwdNum uint64;
    var pkgType string;
    var pkgName string;
    var typeName string;
    var sizeStr string;
    var sizeNum uint64;
    var alignStr string;
    var alignNum uint64;
    var ind uint64;
    var tmp1 uint64;
    var tmp2 uint64;
    var tmpStr1 string;
    var tmpStr2 string;
    // maybe some more flags, indicators, temps, strings, numbers, ... ?!? ;)

    fwdStr = GetNextSymToken(ld);
    fwdNum = libgogo.StringToInt(fwdStr);
    pkgType = GetNextSymToken(ld);
    sizeStr = GetNextSymToken(ld);
    sizeNum = libgogo.StringToInt(sizeStr);
    alignStr = GetNextSymToken(ld);
    alignNum = libgogo.StringToInt(alignStr);
    ind = IsDefaultType(pkgType);
    if ind == 0 { // non-default type, try to add
        pkgName = GetPackageName(pkgType);
        typeName = GetFuncName(pkgType);
        some_t = libgogo.GetType(typeName, pkgName, GlobalTypes, 1);
        if some_t == nil {
            some_t = libgogo.NewType(typeName, pkgName, fwdNum, sizeNum, nil);
            GlobalTypes = libgogo.AppendType(some_t, GlobalTypes);
        } else {
            /* All kings of fwd declaration combinations need to be checked */
            if (some_t.ForwardDecl == 0) && (fwdNum == 0) {
                tmp1 = libgogo.GetTypeSize(some_t);
                tmp2 = libgogo.GetTypeSizeAligned(some_t);
                if (sizeNum == tmp1) && (alignNum == tmp2) {
                    LinkWarn("duplicated type (", pkgType, "). sizes matched.", "", "");
                } else {
                    libgogo.StringAppend(&tmpStr1, "new: ");
                    libgogo.StringAppend(&tmpStr1, sizeStr);
                    libgogo.StringAppend(&tmpStr1, "; defined: ");
                    tmpStr2 = libgogo.IntToString(tmp1);
                    libgogo.StringAppend(&tmpStr1, tmpStr2);
                    LinkError("duplicate type: ", pkgType, ". Incompatible sizes: ",tmpStr1, "");
                }
            }
            if (some_t.ForwardDecl == 1) && (fwdNum == 0) {
                /* Fix the params of the fwd. declared type */
                some_t.ForwardDecl = 0;
                some_t.Len = sizeNum;
            }
            if (some_t.ForwardDecl == 0) && (fwdNum == 1) {
                ; // skip since this is useless
            }
            if (some_t.ForwardDecl == 1) && (fwdNum == 1) {
                ; // skip since this is useless
            }
        }
    }
}

func GetParameterSize(pkgName string, funcName string) uint64 {
    var size uint64;
    var func_t *libgogo.TypeDesc = nil;
    func_t = libgogo.GetType(funcName, pkgName, GlobalFunctions, 1);
    if func_t != nil {
        size = libgogo.GetAlignedObjectListSize(func_t.Fields);
    } else {
        LinkError("unable to look up function '", pkgName, "·", funcName, "' in symtable.");
    }
    return size;
}

//
// Function processing a line and fixing offsets if necessary
//
func FixOffset(ld *LineDesc) string {
    var i uint64;
    var strLen uint64;
    var size uint64;
    var oldsize uint64;
    var numstr string;
    var newLine string;
    if ld.NeedsFix == 1 { // Type 1 fix of offsets
        strLen = libgogo.StringLength(ld.Line);
        size = GetParameterSize(ld.PackageName, ld.FunctionName);
        for i = 0; ld.Line[i] != '-' ; i = i +1 {
            libgogo.CharAppend(&newLine, ld.Line[i]);
        }
        libgogo.CharAppend(&newLine, ld.Line[i]);
        for i = i+1 /*Dismiss '-'*/ ;ld.Line[i]!='(';i=i+1 {
            libgogo.CharAppend(&numstr, ld.Line[i]);
        }
        oldsize = libgogo.StringToInt(numstr);
        size = size + oldsize;
        size = size - 100000;
        numstr = libgogo.IntToString(size);
        libgogo.StringAppend(&newLine, numstr);
        for ; i < strLen; i = i +1 {
            libgogo.CharAppend(&newLine, ld.Line[i]);
        }
    }
    if ld.NeedsFix == 2 { // Type 2 fix of offsets
        strLen = libgogo.StringLength(ld.Line);
        size = GetParameterSize(ld.PackageName, ld.FunctionName);
        for i = 0; ld.Line[i] != '$' ; i = i +1 {
            libgogo.CharAppend(&newLine, ld.Line[i]);
        }
        libgogo.CharAppend(&newLine, ld.Line[i]);
        for i = i+1 /*Dismiss '-'*/ ;ld.Line[i]!=',';i=i+1 {
            libgogo.CharAppend(&numstr, ld.Line[i]);
        }
        oldsize = libgogo.StringToInt(numstr);
        size = size + oldsize;
        numstr = libgogo.IntToString(size);
        libgogo.StringAppend(&newLine, numstr);
        for ; i < strLen; i = i +1 {
            libgogo.CharAppend(&newLine, ld.Line[i]);
        }
    }

    ld.NeedsFix = 0;
    ld.Line = newLine;
    return newLine;
}

//
// Main linking method
//
func Link() {
    var newLine string;
    var strCmp uint64;
    var symtable uint64 = 0;
    var ld LineDesc;

    ResetToken();
    GetLine(&ld);
    for ;tok.id != TOKEN_EOS;{
        strCmp = libgogo.StringCompare("//Symbol table:", ld.Line);
        if strCmp == 0 {
            symtable = 1;
            GetLine(&ld); // Proceed to next line
        }
        if symtable != 0 { // Parse symtable entries
            strCmp = libgogo.StringCompare("//End Symbol table", ld.Line);
            if strCmp == 0 {
                symtable = 0;
            } else {
                ParseSymTblLine(&ld);
            }
        } else { // Parse normal lines and fix everything
            if ld.NeedsFix != 0 {
                GetLine(&ld);
                newLine = FixOffset(&ld);
                libgogo.PrintString(newLine);
                libgogo.PrintString("\n");
            } else {
                strCmp = libgogo.StringCompare(ld.Line, "__UNLINKED_CODE");
                if strCmp != 0 {
                    libgogo.PrintString(ld.Line);
                    libgogo.PrintString("\n");
                }
            }

        }
        GetLine(&ld);
    }
}
<commit_msg>linker: Add first "working" version of the linker<commit_after>// Copyright 2010 The GoGo Authors. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package main

import "./libgogo/_obj/libgogo"

type LineDesc struct {
    Line string;
    Offset uint64;

    NeedsFix uint64;
    PackageName string;
    FunctionName string;
};

type ParamDesc struct {
    Name string;
    TypePackage string;
    TypeName string;
    Ptr uint64;
};

//
// Function fetches the next line from the assembly file.
//
func GetLine(ld *LineDesc) {
    var line string;
    var i uint64 =0;
    var singleChar byte;
    var cmp uint64;

    for singleChar = GetCharWrapped(); (i<10) && (singleChar!=10) && (singleChar!=0); singleChar = GetCharWrapped() {
        libgogo.CharAppend(&line, singleChar);
        i = i +1;
    }
    cmp = libgogo.StringCompare(line, "  //--- ##");
    if cmp == 0 {
        // actual singleChar should be the number
        ld.NeedsFix = libgogo.ToIntFromByte(singleChar);
        ld.NeedsFix = ld.NeedsFix - 48;
        if (ld.NeedsFix != 1) && (ld.NeedsFix != 2) {
            LinkError("encountered internal error when reading fixups. file edited by hand?", "", "", "", "");
        }
        ld.PackageName = "";
        ld.FunctionName = "";

        GetCharWrapped(); // Abolish '#'
        GetCharWrapped(); // Abolish '#'

        for singleChar = GetCharWrapped(); singleChar != 194 ; singleChar = GetCharWrapped() {
            libgogo.CharAppend(&ld.PackageName, singleChar);
        }
        GetCharWrapped(); // Abolish second part of dot (183)
        for singleChar = GetCharWrapped(); (singleChar != '#'); singleChar = GetCharWrapped() {
            libgogo.CharAppend(&ld.FunctionName, singleChar);
        }

        if (singleChar != 10) && (singleChar != 0) {
            for ; (singleChar != 0) && (singleChar != 10); singleChar = GetCharWrapped() {
                libgogo.CharAppend(&line, singleChar);
            }
        }
    } else {
        for ; (singleChar != 0) && (singleChar != 10); singleChar = GetCharWrapped() {
            libgogo.CharAppend(&line, singleChar);
        }
    }

    if singleChar == 0 {
        tok.id = TOKEN_EOS;
    }
    ld.Line = line;
    ld.Offset = 0;

}

func GetNextSymToken(ld *LineDesc) string {
    var symtoken string = "";
    var i uint64;
    var len uint64;
    len = libgogo.StringLength(ld.Line);
    for i = ld.Offset; (i < len) && (ld.Line[i] != ','); i=i+1 {
        if (ld.Line[i] != '/') {
            libgogo.CharAppend(&symtoken, ld.Line[i]);
        }
    }
    ld.Offset = i+1;
    return symtoken;
}

func ParseSymTblLine(ld *LineDesc) {
    var symtype string;
    var strCmp uint64;

    symtype = GetNextSymToken(ld);
    strCmp = libgogo.StringCompare(symtype, "TYPE");
    if strCmp == 0 {
        ParseSymTblType(ld);
    }
    strCmp = libgogo.StringCompare(symtype, "FUNC");
    if strCmp == 0 {
        ParseSymTblFunc(ld);
    }
}

func IsDefaultType(pkgType string) uint64 {
    var strCmp uint64;
    var retValue uint64 = 0;
    strCmp = libgogo.StringCompare(pkgType, "·uint64");
    if strCmp == 0 {
        retValue = 1;
    }
    strCmp = libgogo.StringCompare(pkgType, "·byte");
    if strCmp == 0 {
        retValue = 1;
    }
    strCmp = libgogo.StringCompare(pkgType, "·string");
    if strCmp == 0 {
        retValue = 1;
    }
    strCmp = libgogo.StringCompare(pkgType, "·bool");
    if strCmp == 0 {
        retValue = 1;
    }
    return retValue;
}

func GetPackageName(pkgType string) string {
    var retStr string;
    var i uint64;
    for i = 0; pkgType[i] != 194 ; i=i+1 {
        libgogo.CharAppend(&retStr, pkgType[i]);
    }
    return retStr;
}

func GetFuncName(pkgType string) string {
    var retStr string;
    var i uint64;
    var strLen uint64;
    strLen = libgogo.StringLength(pkgType);
    for i = 0; pkgType[i] != 183; i = i +1 {
    }
    i = i+1;
    for ; i < strLen; i = i +1 {
        libgogo.CharAppend(&retStr, pkgType[i]);
    }
    return retStr;
}

func ParseSymbolParam(pd *ParamDesc, paramType string) {
    var i uint64 = 0;
    var strLen uint64;

    pd.Name = "";
    pd.TypePackage = "";
    pd.TypeName = "";
    pd.Ptr = 0;

    strLen = libgogo.StringLength(paramType);
    if strLen > 0 {
        for i = 0; paramType[i] != ':' ; i=i+1 {
            libgogo.CharAppend(&pd.Name, paramType[i]);
        }
        i=i+1;
        if paramType[i] == '*' {
            pd.Ptr = 1;
            i=i+1;
        }
        for ;paramType[i] != 194; i=i+1 {
            libgogo.CharAppend(&pd.TypePackage, paramType[i]);
        }
        for i=i+2;i < strLen; i=i+1 {
            libgogo.CharAppend(&pd.TypeName, paramType[i]);
        }
    }
}

//
// Function parsing the main part of a function in a symbol table and adding it
// to the global symbol table.
//
func ParseSymTblFunc(ld *LineDesc) {
    var pd ParamDesc;
    var func_t *libgogo.TypeDesc = nil;
    var some_t *libgogo.TypeDesc = nil;
    var fwdStr string;
    var fwdNum uint64;
    var pkgFunc string;
    var funcName string;
    var pkgName string;
    var ind uint64;
    var paramType string;
    var tmpParam *libgogo.ObjectDesc;

    fwdStr = GetNextSymToken(ld);
    fwdNum = libgogo.StringToInt(fwdStr);
    pkgFunc = GetNextSymToken(ld);
    ind = libgogo.StringCompare(pkgFunc, "main·init");
    if ind != 0 {
        pkgName = GetPackageName(pkgFunc);
        funcName = GetFuncName(pkgFunc);
        func_t = NewFunction(funcName, pkgName, fwdNum);
        paramType = GetNextSymToken(ld);
        ind = libgogo.StringLength(paramType);
        for ; ind != 0 ; {
            ParseSymbolParam(&pd, paramType);
            tmpParam = libgogo.NewObject(pd.Name, "", libgogo.CLASS_PARAMETER);
            some_t = libgogo.GetType(pd.TypeName, pd.TypePackage, GlobalTypes, 1);
            if some_t != nil {
                tmpParam.ObjType = some_t;
                tmpParam.PtrType = pd.Ptr;
                libgogo.AddParameters(tmpParam, func_t);
            } else {
                LinkError("unable to find type '",pd.TypePackage,"·",pd.TypeName,"'.");
            }
            paramType = GetNextSymToken(ld);
            ind = libgogo.StringLength(paramType);
        }
    }

}

//
// Function parsing the main part of a type in a symbol table and adding it
// to the global symbol table.
//
func ParseSymTblType(ld *LineDesc) {
    var some_t *libgogo.TypeDesc = nil;
    var fwdStr string;
    var fwdNum uint64;
    var pkgType string;
    var pkgName string;
    var typeName string;
    var sizeStr string;
    var sizeNum uint64;
    var alignStr string;
    var alignNum uint64;
    var ind uint64;
    var tmp1 uint64;
    var tmp2 uint64;
    var tmpStr1 string;
    var tmpStr2 string;
    // maybe some more flags, indicators, temps, strings, numbers, ... ?!? ;)

    fwdStr = GetNextSymToken(ld);
    fwdNum = libgogo.StringToInt(fwdStr);
    pkgType = GetNextSymToken(ld);
    sizeStr = GetNextSymToken(ld);
    sizeNum = libgogo.StringToInt(sizeStr);
    alignStr = GetNextSymToken(ld);
    alignNum = libgogo.StringToInt(alignStr);
    ind = IsDefaultType(pkgType);
    if ind == 0 { // non-default type, try to add
        pkgName = GetPackageName(pkgType);
        typeName = GetFuncName(pkgType);
        some_t = libgogo.GetType(typeName, pkgName, GlobalTypes, 1);
        if some_t == nil {
            some_t = libgogo.NewType(typeName, pkgName, fwdNum, sizeNum, nil);
            GlobalTypes = libgogo.AppendType(some_t, GlobalTypes);
        } else {
            /* All kings of fwd declaration combinations need to be checked */
            if (some_t.ForwardDecl == 0) && (fwdNum == 0) {
                tmp1 = libgogo.GetTypeSize(some_t);
                tmp2 = libgogo.GetTypeSizeAligned(some_t);
                if (sizeNum == tmp1) && (alignNum == tmp2) {
                    LinkWarn("duplicated type (", pkgType, "). sizes matched.", "", "");
                } else {
                    libgogo.StringAppend(&tmpStr1, "new: ");
                    libgogo.StringAppend(&tmpStr1, sizeStr);
                    libgogo.StringAppend(&tmpStr1, "; defined: ");
                    tmpStr2 = libgogo.IntToString(tmp1);
                    libgogo.StringAppend(&tmpStr1, tmpStr2);
                    LinkError("duplicate type: ", pkgType, ". Incompatible sizes: ",tmpStr1, "");
                }
            }
            if (some_t.ForwardDecl == 1) && (fwdNum == 0) {
                /* Fix the params of the fwd. declared type */
                some_t.ForwardDecl = 0;
                some_t.Len = sizeNum;
            }
            if (some_t.ForwardDecl == 0) && (fwdNum == 1) {
                ; // skip since this is useless
            }
            if (some_t.ForwardDecl == 1) && (fwdNum == 1) {
                ; // skip since this is useless
            }
        }
    }
}

func GetParameterSize(pkgName string, funcName string) uint64 {
    var size uint64;
    var func_t *libgogo.TypeDesc = nil;
    func_t = libgogo.GetType(funcName, pkgName, GlobalFunctions, 1);
    if func_t != nil {
        size = libgogo.GetAlignedObjectListSize(func_t.Fields);
    } else {
        LinkError("unable to look up function '", pkgName, "·", funcName, "' in symtable.");
    }
    return size;
}

//
// Function processing a line and fixing offsets if necessary
//
func FixOffset(ld *LineDesc) string {
    var i uint64;
    var strLen uint64;
    var size uint64;
    var oldsize uint64;
    var numstr string;
    var newLine string;

    if ld.NeedsFix == 1 { // Type 1 fix of offsets
        strLen = libgogo.StringLength(ld.Line);
        size = GetParameterSize(ld.PackageName, ld.FunctionName);
        for i = 0; ld.Line[i] != '-' ; i = i +1 {
            libgogo.CharAppend(&newLine, ld.Line[i]);
        }
        libgogo.CharAppend(&newLine, ld.Line[i]);
        for i = i+1  ;ld.Line[i]!='(';i=i+1 {//Dismiss '-'
            libgogo.CharAppend(&numstr, ld.Line[i]);
        }
        oldsize = libgogo.StringToInt(numstr);
        size = size + oldsize;
        size = size - 100000;
        numstr = libgogo.IntToString(size);
        libgogo.StringAppend(&newLine, numstr);
        for ; i < strLen; i = i +1 {
            libgogo.CharAppend(&newLine, ld.Line[i]);
        }
    }
    if ld.NeedsFix == 2 { // Type 2 fix of offsets
        strLen = libgogo.StringLength(ld.Line);
        size = GetParameterSize(ld.PackageName, ld.FunctionName);
        for i = 0; ld.Line[i] != '$' ; i = i +1 {
            libgogo.CharAppend(&newLine, ld.Line[i]);
        }

        libgogo.CharAppend(&newLine, ld.Line[i]);
        for i = i+1  ;ld.Line[i]!=',';i=i+1 {//Dismiss '-'
            libgogo.CharAppend(&numstr, ld.Line[i]);
        }
        oldsize = libgogo.StringToInt(numstr);
        size = size + oldsize;
        numstr = libgogo.IntToString(size);
        libgogo.StringAppend(&newLine, numstr);
        for ; i < strLen; i = i +1 {
            libgogo.CharAppend(&newLine, ld.Line[i]);
        }

    }
    ld.NeedsFix = 0;
    ld.Line = newLine;
    return newLine;
}

//
// Main linking method
//
func Link() {
    var newLine string;
    var strCmp uint64;
    var symtable uint64 = 0;
    var ld LineDesc;

    ResetToken();
    GetLine(&ld);
    for ;tok.id != TOKEN_EOS;{
        strCmp = libgogo.StringCompare("//Symbol table:", ld.Line);
        if strCmp == 0 {
            symtable = 1;
            GetLine(&ld); // Proceed to next line
        }
        if symtable != 0 { // Parse symtable entries
            strCmp = libgogo.StringCompare("//End Symbol table", ld.Line);
            if strCmp == 0 {
                symtable = 0;
            } else {
                ParseSymTblLine(&ld);
            }
        } else { // Parse normal lines and fix everything
            if ld.NeedsFix != 0 {
                GetLine(&ld);
                newLine = FixOffset(&ld);
                //libgogo.PrintString("...");
                //libgogo.PrintString(ld.Line);
                //libgogo.PrintString("\n");
                ld.NeedsFix = 0;
                libgogo.PrintString(newLine);
                libgogo.PrintString("\n");
            } else {
                strCmp = libgogo.StringCompare(ld.Line, "__UNLINKED_CODE");
                if strCmp != 0 {
                    libgogo.PrintString(ld.Line);
                    libgogo.PrintString("\n");
                }
            }

        }
        GetLine(&ld);
    }
}
<|endoftext|>


<commit_before>// Package metric implements the queue metric for NSQ (http://nsq.io/).
package metric

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"os"
	"time"
)

const constNSQStatsEndpoint string = "127.0.0.1:4151"
const constNSQStatsAPI string = "/stats?format=json"

// compile-time assert that we implement the right interface
var _ Metric = (*NSQMetric)(nil)

// NSQMetric stores the current value.
type NSQMetric struct {
	currentVal  int
	topicName   string
	channelName string
}

// StatsMessage from NSQ stats API.
type StatsMessage struct {
	Data StatsData `json:"data"`
}

// StatsData from NSQ stats API.
type StatsData struct {
	Topics []Topic `json:"topics"`
}

// Topic from NSQ stats API.
type Topic struct {
	TopicName string    `json:"topic_name"`
	Channels  []Channel `json:"channels"`
}

// Channel from NSQ stats API.
type Channel struct {
	ChannelName string `json:"channel_name"`
	Depth       int    `json:"depth"`
}

var (
	httpClient = &http.Client{
		// TODO Make timeout configurable.
		Timeout: 10 * time.Second,
	}
	nsqStatsEndpoint string
	nsqInitialized   = false
)

// NSQInit sets up the NSQ Stats endpoint.
func NSQInit() {
	nsqStatsEndpoint = os.Getenv("NSQ_STATS_ENDPOINT")
	if nsqStatsEndpoint == "" {
		nsqStatsEndpoint = constNSQStatsEndpoint
	}

	nsqInitialized = true
	return
}

// NewNSQMetric creates the metric.
func NewNSQMetric(topicName string, channelName string) *NSQMetric {
	if !nsqInitialized {
		NSQInit()
	}

	return &NSQMetric{
		topicName:   topicName,
		channelName: channelName,
	}
}

// Call NSQ Stats API to get the queue length.
func getJSONGet(url string) (body []byte, err error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Errorf("Failed to build API GET request err %v", err)
		return nil, err
	}

	req.Header.Set("Content-Type", "application/json")
	resp, err := httpClient.Do(req)
	if err != nil {
		log.Errorf("Failed to GET err %v", err)
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		log.Errorf("Http error %d: %s", resp.StatusCode, resp.Status)
	}

	body, err = ioutil.ReadAll(resp.Body)

	return body, err
}

// UpdateCurrent sets the current queue length.
func (nsqm *NSQMetric) UpdateCurrent() {
	var statsMessage StatsMessage

	url := "http://" + nsqStatsEndpoint + constNSQStatsAPI
	body, err := getJSONGet(url)
	if err != nil {
		log.Errorf("Error getting NSQ metric %v", err)
	}

	err = json.Unmarshal(body, &statsMessage)
	if err != nil {
		log.Errorf("Error %v unmarshalling from %s", err, string(body[:]))
	}

	// Loop through NSQ Channels and Metrics to find the correct value.
	for _, topic := range statsMessage.Data.Topics {
		if topic.TopicName == nsqm.topicName {
			for _, channel := range topic.Channels {
				if channel.ChannelName == nsqm.channelName {
					nsqm.currentVal = channel.Depth
				}
			}
		}
	}

	log.Debugf("Topic: %s Channel: %s Length: %d", nsqm.topicName, nsqm.channelName, nsqm.currentVal)
}

// Current returns the queue length.
func (nsqm *NSQMetric) Current() int {
	return nsqm.currentVal
}
<commit_msg>Use utils.GetJSON for getting NSQ queue length.<commit_after>// Package metric implements the queue metric for NSQ (http://nsq.io/).
package metric

import (
	"encoding/json"
	"os"

	"github.com/microscaling/microscaling/utils"
)

const constNSQStatsEndpoint string = "127.0.0.1:4151"
const constNSQStatsAPI string = "/stats?format=json"

// compile-time assert that we implement the right interface
var _ Metric = (*NSQMetric)(nil)

// NSQMetric stores the current value.
type NSQMetric struct {
	currentVal  int
	topicName   string
	channelName string
}

// StatsMessage from NSQ stats API.
type StatsMessage struct {
	Data StatsData `json:"data"`
}

// StatsData from NSQ stats API.
type StatsData struct {
	Topics []Topic `json:"topics"`
}

// Topic from NSQ stats API.
type Topic struct {
	TopicName string    `json:"topic_name"`
	Channels  []Channel `json:"channels"`
}

// Channel from NSQ stats API.
type Channel struct {
	ChannelName string `json:"channel_name"`
	Depth       int    `json:"depth"`
}

var (
	nsqStatsEndpoint string
	nsqInitialized   = false
)

// NSQInit sets up the NSQ Stats endpoint.
func NSQInit() {
	nsqStatsEndpoint = os.Getenv("NSQ_STATS_ENDPOINT")
	if nsqStatsEndpoint == "" {
		nsqStatsEndpoint = constNSQStatsEndpoint
	}

	nsqInitialized = true
	return
}

// NewNSQMetric creates the metric.
func NewNSQMetric(topicName string, channelName string) *NSQMetric {
	if !nsqInitialized {
		NSQInit()
	}

	return &NSQMetric{
		topicName:   topicName,
		channelName: channelName,
	}
}

// UpdateCurrent sets the current queue length.
func (nsqm *NSQMetric) UpdateCurrent() {
	var statsMessage StatsMessage

	url := "http://" + nsqStatsEndpoint + constNSQStatsAPI
	body, err := utils.GetJSON(url)
	if err != nil {
		log.Errorf("Error getting NSQ metric %v", err)
	}

	err = json.Unmarshal(body, &statsMessage)
	if err != nil {
		log.Errorf("Error %v unmarshalling from %s", err, string(body[:]))
	}

	// Loop through NSQ Channels and Metrics to find the correct value.
	for _, topic := range statsMessage.Data.Topics {
		if topic.TopicName == nsqm.topicName {
			for _, channel := range topic.Channels {
				if channel.ChannelName == nsqm.channelName {
					nsqm.currentVal = channel.Depth
				}
			}
		}
	}

	log.Debugf("Topic: %s Channel: %s Length: %d", nsqm.topicName, nsqm.channelName, nsqm.currentVal)
}

// Current returns the queue length.
func (nsqm *NSQMetric) Current() int {
	return nsqm.currentVal
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/codepipeline"
	"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

func TestAccAWSCodePipelineWebhook_basic(t *testing.T) {
	var v codepipeline.ListWebhookItem
	rName := acctest.RandomWithPrefix("tf-acc-test")
	resourceName := "aws_codepipeline_webhook.test"
	pipelineResourceName := "aws_codepipeline.test"

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCodePipelineDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCodePipelineWebhookConfig_basic(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineExists(pipelineResourceName),
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttrSet(resourceName, "url"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.#", "1"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.0.secret_token", "super-secret"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSCodePipelineWebhook_ipAuth(t *testing.T) {
	var v codepipeline.ListWebhookItem
	rName := acctest.RandomWithPrefix("tf-acc-test")
	resourceName := "aws_codepipeline_webhook.test"
	pipelineResourceName := "aws_codepipeline.test"

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCodePipelineDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCodePipelineWebhookConfig_ipAuth(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineExists(pipelineResourceName),
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttrSet(resourceName, "url"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.#", "1"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.0.allowed_ip_range", "0.0.0.0/0"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSCodePipelineWebhook_unauthenticated(t *testing.T) {
	var v codepipeline.ListWebhookItem
	rName := acctest.RandomWithPrefix("tf-acc-test")
	resourceName := "aws_codepipeline_webhook.test"
	pipelineResourceName := "aws_codepipeline.test"

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCodePipelineDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCodePipelineWebhookConfig_unauthenticated(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineExists(pipelineResourceName),
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttrSet(resourceName, "url"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSCodePipelineWebhook_tags(t *testing.T) {
	var v1, v2, v3 codepipeline.ListWebhookItem
	rName := acctest.RandomWithPrefix("tf-acc-test")
	resourceName := "aws_codepipeline_webhook.test"
	pipelineResourceName := "aws_codepipeline.test"

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCodePipelineDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCodePipelineWebhookConfigWithTags(rName, "tag1value", "tag2value"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineExists(pipelineResourceName),
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v1),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "3"),
					resource.TestCheckResourceAttr(resourceName, "tags.Name", rName),
					resource.TestCheckResourceAttr(resourceName, "tags.tag1", "tag1value"),
					resource.TestCheckResourceAttr(resourceName, "tags.tag2", "tag2value"),
				),
			},
			{
				Config: testAccAWSCodePipelineWebhookConfigWithTags(rName, "tag1valueUpdate", "tag2valueUpdate"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineExists(pipelineResourceName),
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v2),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "3"),
					resource.TestCheckResourceAttr(resourceName, "tags.Name", rName),
					resource.TestCheckResourceAttr(resourceName, "tags.tag1", "tag1valueUpdate"),
					resource.TestCheckResourceAttr(resourceName, "tags.tag2", "tag2valueUpdate"),
					func(s *terraform.State) error {
						if aws.StringValue(v2.Url) != aws.StringValue(v1.Url) {
							return fmt.Errorf("Codepipeline webhook recreated when changing tags")
						}
						return nil
					},
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccAWSCodePipelineWebhookConfig_basic(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineExists(pipelineResourceName),
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v3),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
					func(s *terraform.State) error {
						if aws.StringValue(v3.Url) != aws.StringValue(v2.Url) {
							return fmt.Errorf("Codepipeline webhook recreated when deleting tags")
						}
						return nil
					},
				),
			},
		},
	})
}

func TestAccAWSCodePipelineWebhook_UpdateAuthenticationConfiguration_SecretToken(t *testing.T) {
	var v1, v2 codepipeline.ListWebhookItem
	rName := acctest.RandomWithPrefix("tf-acc-test")
	resourceName := "aws_codepipeline_webhook.test"
	pipelineResourceName := "aws_codepipeline.test"

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCodePipelineDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCodePipelineWebhookConfig_basic(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineExists(pipelineResourceName),
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v1),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttrSet(resourceName, "url"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.#", "1"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.0.secret_token", "super-secret"),
				),
			},
			{
				Config: testAccAWSCodePipelineWebhookConfig_secretTokenUpdated(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineExists(pipelineResourceName),
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v2),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttrSet(resourceName, "url"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.#", "1"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.0.secret_token", "even-more-secret"),
					func(s *terraform.State) error {
						if aws.StringValue(v2.Url) == aws.StringValue(v1.Url) {
							return fmt.Errorf("Codepipeline webhook not recreated when updating authentication_configuration.secret_token")
						}
						return nil
					},
				),
			},
		},
	})
}

func testAccCheckAWSCodePipelineWebhookExists(n string, webhook *codepipeline.ListWebhookItem) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No webhook ARN is set as ID")
		}

		conn := testAccProvider.Meta().(*AWSClient).codepipelineconn

		resp, err := getCodePipelineWebhook(conn, rs.Primary.ID)

		if err != nil {
			return err
		}

		*webhook = *resp

		return nil
	}
}

func testAccAWSCodePipelineWebhookConfig_basic(rName string) string {
	return testAccAWSCodePipelineWebhookConfig_codePipeline(rName) + fmt.Sprintf(`
resource "aws_codepipeline_webhook" "test" {
  name            = %[1]q
  authentication  = "GITHUB_HMAC"
  target_action   = "Source"
  target_pipeline = "${aws_codepipeline.test.name}"

  authentication_configuration {
    secret_token = "super-secret"
  }

  filter {
    json_path    = "$.ref"
    match_equals = "refs/head/{Branch}"
  }
}
`, rName)
}

func testAccAWSCodePipelineWebhookConfig_ipAuth(rName string) string {
	return testAccAWSCodePipelineWebhookConfig_codePipeline(rName) + fmt.Sprintf(`
resource "aws_codepipeline_webhook" "test" {
  name            = %[1]q
  authentication  = "IP"
  target_action   = "Source"
  target_pipeline = "${aws_codepipeline.test.name}"

  authentication_configuration {
    allowed_ip_range = "0.0.0.0/0"
  }

  filter {
    json_path    = "$.ref"
    match_equals = "refs/head/{Branch}"
  }
}
`, rName)
}

func testAccAWSCodePipelineWebhookConfig_unauthenticated(rName string) string {
	return testAccAWSCodePipelineWebhookConfig_codePipeline(rName) + fmt.Sprintf(`
resource "aws_codepipeline_webhook" "test" {
  name            = %[1]q
  authentication  = "UNAUTHENTICATED"
  target_action   = "Source"
  target_pipeline = "${aws_codepipeline.test.name}"

  filter {
    json_path    = "$.ref"
    match_equals = "refs/head/{Branch}"
  }
}
`, rName)
}

func testAccAWSCodePipelineWebhookConfigWithTags(rName, tag1, tag2 string) string {
	return testAccAWSCodePipelineWebhookConfig_codePipeline(rName) + fmt.Sprintf(`
resource "aws_codepipeline_webhook" "test" {
  name            = %[1]q
  authentication  = "GITHUB_HMAC"
  target_action   = "Source"
  target_pipeline = "${aws_codepipeline.test.name}"

  authentication_configuration {
    secret_token = "super-secret"
  }

  filter {
    json_path    = "$.ref"
    match_equals = "refs/head/{Branch}"
  }

  tags = {
    Name = %[1]q
    tag1 = %[2]q
    tag2 = %[3]q
  }
}
`, rName, tag1, tag2)
}

func testAccAWSCodePipelineWebhookConfig_secretTokenUpdated(rName string) string {
	return testAccAWSCodePipelineWebhookConfig_codePipeline(rName) + fmt.Sprintf(`
resource "aws_codepipeline_webhook" "test" {
  name            = %[1]q
  authentication  = "GITHUB_HMAC"
  target_action   = "Source"
  target_pipeline = "${aws_codepipeline.test.name}"

  authentication_configuration {
    secret_token = "even-more-secret"
  }

  filter {
    json_path    = "$.ref"
    match_equals = "refs/head/{Branch}"
  }
}
`, rName)
}

func testAccAWSCodePipelineWebhookConfig_codePipeline(rName string) string {
	return fmt.Sprintf(`
resource "aws_s3_bucket" "test" {
  bucket = %[1]q
  acl    = "private"
}

resource "aws_iam_role" "test" {
  name = %[1]q

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "codepipeline.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "test" {
  name = %[1]q
  role = "${aws_iam_role.test.id}"

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect":"Allow",
      "Action": [
        "s3:GetObject",
        "s3:GetObjectVersion",
        "s3:GetBucketVersioning"
      ],
      "Resource": [
        "${aws_s3_bucket.test.arn}",
        "${aws_s3_bucket.test.arn}/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "codebuild:BatchGetBuilds",
        "codebuild:StartBuild"
      ],
      "Resource": "*"
    }
  ]
}
EOF
}

resource "aws_codepipeline" "test" {
  name     = %[1]q
  role_arn = "${aws_iam_role.test.arn}"

  artifact_store {
    location = "${aws_s3_bucket.test.bucket}"
    type     = "S3"

    encryption_key {
      id   = "1234"
      type = "KMS"
    }
  }

  stage {
    name = "Source"

    action {
      name             = "Source"
      category         = "Source"
      owner            = "ThirdParty"
      provider         = "GitHub"
      version          = "1"
      output_artifacts = ["test"]

      configuration = {
        Owner  = "lifesum-terraform"
        Repo   = "test"
        Branch = "master"
      }
    }
  }

  stage {
    name = "Build"

    action {
      name            = "Build"
      category        = "Build"
      owner           = "AWS"
      provider        = "CodeBuild"
      input_artifacts = ["test"]
      version         = "1"

      configuration = {
        ProjectName = "test"
      }
    }
  }
}
`, rName)
}
<commit_msg>Removes redundant CodePipelineExists test from CodePipleline webhook tests<commit_after>package aws

import (
	"fmt"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/codepipeline"
	"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

func TestAccAWSCodePipelineWebhook_basic(t *testing.T) {
	var v codepipeline.ListWebhookItem
	rName := acctest.RandomWithPrefix("tf-acc-test")
	resourceName := "aws_codepipeline_webhook.test"

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCodePipelineDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCodePipelineWebhookConfig_basic(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttrSet(resourceName, "url"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.#", "1"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.0.secret_token", "super-secret"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSCodePipelineWebhook_ipAuth(t *testing.T) {
	var v codepipeline.ListWebhookItem
	rName := acctest.RandomWithPrefix("tf-acc-test")
	resourceName := "aws_codepipeline_webhook.test"

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCodePipelineDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCodePipelineWebhookConfig_ipAuth(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttrSet(resourceName, "url"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.#", "1"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.0.allowed_ip_range", "0.0.0.0/0"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSCodePipelineWebhook_unauthenticated(t *testing.T) {
	var v codepipeline.ListWebhookItem
	rName := acctest.RandomWithPrefix("tf-acc-test")
	resourceName := "aws_codepipeline_webhook.test"

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCodePipelineDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCodePipelineWebhookConfig_unauthenticated(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttrSet(resourceName, "url"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSCodePipelineWebhook_tags(t *testing.T) {
	var v1, v2, v3 codepipeline.ListWebhookItem
	rName := acctest.RandomWithPrefix("tf-acc-test")
	resourceName := "aws_codepipeline_webhook.test"

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCodePipelineDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCodePipelineWebhookConfigWithTags(rName, "tag1value", "tag2value"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v1),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "3"),
					resource.TestCheckResourceAttr(resourceName, "tags.Name", rName),
					resource.TestCheckResourceAttr(resourceName, "tags.tag1", "tag1value"),
					resource.TestCheckResourceAttr(resourceName, "tags.tag2", "tag2value"),
				),
			},
			{
				Config: testAccAWSCodePipelineWebhookConfigWithTags(rName, "tag1valueUpdate", "tag2valueUpdate"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v2),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "3"),
					resource.TestCheckResourceAttr(resourceName, "tags.Name", rName),
					resource.TestCheckResourceAttr(resourceName, "tags.tag1", "tag1valueUpdate"),
					resource.TestCheckResourceAttr(resourceName, "tags.tag2", "tag2valueUpdate"),
					func(s *terraform.State) error {
						if aws.StringValue(v2.Url) != aws.StringValue(v1.Url) {
							return fmt.Errorf("Codepipeline webhook recreated when changing tags")
						}
						return nil
					},
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccAWSCodePipelineWebhookConfig_basic(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v3),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
					func(s *terraform.State) error {
						if aws.StringValue(v3.Url) != aws.StringValue(v2.Url) {
							return fmt.Errorf("Codepipeline webhook recreated when deleting tags")
						}
						return nil
					},
				),
			},
		},
	})
}

func TestAccAWSCodePipelineWebhook_UpdateAuthenticationConfiguration_SecretToken(t *testing.T) {
	var v1, v2 codepipeline.ListWebhookItem
	rName := acctest.RandomWithPrefix("tf-acc-test")
	resourceName := "aws_codepipeline_webhook.test"

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSCodePipeline(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSCodePipelineDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSCodePipelineWebhookConfig_basic(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v1),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttrSet(resourceName, "url"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.#", "1"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.0.secret_token", "super-secret"),
				),
			},
			{
				Config: testAccAWSCodePipelineWebhookConfig_secretTokenUpdated(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSCodePipelineWebhookExists(resourceName, &v2),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttrSet(resourceName, "url"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.#", "1"),
					resource.TestCheckResourceAttr(resourceName, "authentication_configuration.0.secret_token", "even-more-secret"),
					func(s *terraform.State) error {
						if aws.StringValue(v2.Url) == aws.StringValue(v1.Url) {
							return fmt.Errorf("Codepipeline webhook not recreated when updating authentication_configuration.secret_token")
						}
						return nil
					},
				),
			},
		},
	})
}

func testAccCheckAWSCodePipelineWebhookExists(n string, webhook *codepipeline.ListWebhookItem) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No webhook ARN is set as ID")
		}

		conn := testAccProvider.Meta().(*AWSClient).codepipelineconn

		resp, err := getCodePipelineWebhook(conn, rs.Primary.ID)

		if err != nil {
			return err
		}

		*webhook = *resp

		return nil
	}
}

func testAccAWSCodePipelineWebhookConfig_basic(rName string) string {
	return testAccAWSCodePipelineWebhookConfig_codePipeline(rName) + fmt.Sprintf(`
resource "aws_codepipeline_webhook" "test" {
  name            = %[1]q
  authentication  = "GITHUB_HMAC"
  target_action   = "Source"
  target_pipeline = "${aws_codepipeline.test.name}"

  authentication_configuration {
    secret_token = "super-secret"
  }

  filter {
    json_path    = "$.ref"
    match_equals = "refs/head/{Branch}"
  }
}
`, rName)
}

func testAccAWSCodePipelineWebhookConfig_ipAuth(rName string) string {
	return testAccAWSCodePipelineWebhookConfig_codePipeline(rName) + fmt.Sprintf(`
resource "aws_codepipeline_webhook" "test" {
  name            = %[1]q
  authentication  = "IP"
  target_action   = "Source"
  target_pipeline = "${aws_codepipeline.test.name}"

  authentication_configuration {
    allowed_ip_range = "0.0.0.0/0"
  }

  filter {
    json_path    = "$.ref"
    match_equals = "refs/head/{Branch}"
  }
}
`, rName)
}

func testAccAWSCodePipelineWebhookConfig_unauthenticated(rName string) string {
	return testAccAWSCodePipelineWebhookConfig_codePipeline(rName) + fmt.Sprintf(`
resource "aws_codepipeline_webhook" "test" {
  name            = %[1]q
  authentication  = "UNAUTHENTICATED"
  target_action   = "Source"
  target_pipeline = "${aws_codepipeline.test.name}"

  filter {
    json_path    = "$.ref"
    match_equals = "refs/head/{Branch}"
  }
}
`, rName)
}

func testAccAWSCodePipelineWebhookConfigWithTags(rName, tag1, tag2 string) string {
	return testAccAWSCodePipelineWebhookConfig_codePipeline(rName) + fmt.Sprintf(`
resource "aws_codepipeline_webhook" "test" {
  name            = %[1]q
  authentication  = "GITHUB_HMAC"
  target_action   = "Source"
  target_pipeline = "${aws_codepipeline.test.name}"

  authentication_configuration {
    secret_token = "super-secret"
  }

  filter {
    json_path    = "$.ref"
    match_equals = "refs/head/{Branch}"
  }

  tags = {
    Name = %[1]q
    tag1 = %[2]q
    tag2 = %[3]q
  }
}
`, rName, tag1, tag2)
}

func testAccAWSCodePipelineWebhookConfig_secretTokenUpdated(rName string) string {
	return testAccAWSCodePipelineWebhookConfig_codePipeline(rName) + fmt.Sprintf(`
resource "aws_codepipeline_webhook" "test" {
  name            = %[1]q
  authentication  = "GITHUB_HMAC"
  target_action   = "Source"
  target_pipeline = "${aws_codepipeline.test.name}"

  authentication_configuration {
    secret_token = "even-more-secret"
  }

  filter {
    json_path    = "$.ref"
    match_equals = "refs/head/{Branch}"
  }
}
`, rName)
}

func testAccAWSCodePipelineWebhookConfig_codePipeline(rName string) string {
	return fmt.Sprintf(`
resource "aws_s3_bucket" "test" {
  bucket = %[1]q
  acl    = "private"
}

resource "aws_iam_role" "test" {
  name = %[1]q

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "codepipeline.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "test" {
  name = %[1]q
  role = "${aws_iam_role.test.id}"

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect":"Allow",
      "Action": [
        "s3:GetObject",
        "s3:GetObjectVersion",
        "s3:GetBucketVersioning"
      ],
      "Resource": [
        "${aws_s3_bucket.test.arn}",
        "${aws_s3_bucket.test.arn}/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "codebuild:BatchGetBuilds",
        "codebuild:StartBuild"
      ],
      "Resource": "*"
    }
  ]
}
EOF
}

resource "aws_codepipeline" "test" {
  name     = %[1]q
  role_arn = "${aws_iam_role.test.arn}"

  artifact_store {
    location = "${aws_s3_bucket.test.bucket}"
    type     = "S3"

    encryption_key {
      id   = "1234"
      type = "KMS"
    }
  }

  stage {
    name = "Source"

    action {
      name             = "Source"
      category         = "Source"
      owner            = "ThirdParty"
      provider         = "GitHub"
      version          = "1"
      output_artifacts = ["test"]

      configuration = {
        Owner  = "lifesum-terraform"
        Repo   = "test"
        Branch = "master"
      }
    }
  }

  stage {
    name = "Build"

    action {
      name            = "Build"
      category        = "Build"
      owner           = "AWS"
      provider        = "CodeBuild"
      input_artifacts = ["test"]
      version         = "1"

      configuration = {
        ProjectName = "test"
      }
    }
  }
}
`, rName)
}
<|endoftext|>


<commit_before>/*
Copyright 2012 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// The genfileembed command embeds resources into Go files, to eliminate run-time
// dependencies on files on the filesystem.
package main

import (
	"bytes"
	"compress/zlib"
	"crypto/sha1"
	"encoding/base64"
	"flag"
	"fmt"
	"go/parser"
	"go/printer"
	"go/token"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"camlistore.org/pkg/rollsum"
)

var (
	processAll = flag.Bool("all", false, "process all files (if false, only process modified files)")

	fileEmbedPkgPath = flag.String("fileembed-package", "camlistore.org/pkg/fileembed", "the Go package name for fileembed. If you have vendored fileembed (e.g. with goven), you can use this flag to ensure that generated code imports the vendored package.")

	chunkThreshold = flag.Int64("chunk-threshold", 0, "If non-zero, the maximum size of a file before it's cut up into content-addressable chunks with a rolling checksum")
	chunkPackage   = flag.String("chunk-package", "", "Package to hold chunks")
)

const (
	maxUncompressed = 50 << 10 // 50KB
	// Threshold ratio for compression.
	// Files which don't compress at least as well are kept uncompressed.
	zRatio = 0.5
)

func usage() {
	fmt.Fprintf(os.Stderr, "usage: genfileembed [flags] [<dir>]\n")
	flag.PrintDefaults()
	os.Exit(2)
}

func main() {
	flag.Usage = usage
	flag.Parse()

	dir := "."
	switch flag.NArg() {
	case 0:
	case 1:
		dir = flag.Arg(0)
		if err := os.Chdir(dir); err != nil {
			log.Fatalf("chdir(%q) = %v", dir, err)
		}
	default:
		flag.Usage()
	}

	pkgName, filePattern, fileEmbedModTime, err := parseFileEmbed()
	if err != nil {
		log.Fatalf("Error parsing %s/fileembed.go: %v", dir, err)
	}

	for _, fileName := range matchingFiles(filePattern) {
		fi, err := os.Stat(fileName)
		if err != nil {
			log.Fatal(err)
		}

		embedName := "zembed_" + fileName + ".go"
		zfi, zerr := os.Stat(embedName)
		genFile := func() bool {
			if *processAll || zerr != nil {
				return true
			}
			if zfi.ModTime().Before(fi.ModTime()) {
				return true
			}
			if zfi.ModTime().Before(fileEmbedModTime) {
				return true
			}
			return false
		}
		if !genFile() {
			continue
		}
		log.Printf("Updating %s (package %s)", filepath.Join(dir, embedName), pkgName)

		bs, err := ioutil.ReadFile(fileName)
		if err != nil {
			log.Fatal(err)
		}

		zb, fileSize := compressFile(bytes.NewReader(bs))
		ratio := float64(len(zb)) / float64(fileSize)
		byteStreamType := ""
		var qb []byte // quoted string, or Go expression evaluating to a string
		var imports string
		if *chunkThreshold > 0 && int64(len(bs)) > *chunkThreshold {
			byteStreamType = "fileembed.Multi"
			qb = chunksOf(bs)
			if *chunkPackage == "" {
				log.Fatalf("Must provide a --chunk-package value with --chunk-threshold")
			}
			imports = fmt.Sprintf("import chunkpkg \"%s\"\n", *chunkPackage)
		} else if fileSize < maxUncompressed || ratio > zRatio {
			byteStreamType = "fileembed.String"
			qb = quote(bs)
		} else {
			byteStreamType = "fileembed.ZlibCompressedBase64"
			qb = quote([]byte(base64.StdEncoding.EncodeToString(zb)))
		}

		var b bytes.Buffer
		fmt.Fprintf(&b, "// THIS FILE IS AUTO-GENERATED FROM %s\n", fileName)
		fmt.Fprintf(&b, "// DO NOT EDIT.\n\n")
		fmt.Fprintf(&b, "package %s\n\n", pkgName)
		fmt.Fprintf(&b, "import \"time\"\n\n")
		fmt.Fprintf(&b, "import \""+*fileEmbedPkgPath+"\"\n\n")
		b.WriteString(imports)
		fmt.Fprintf(&b, "func init() {\n\tFiles.Add(%q, %d, time.Unix(0, %d), %s(%s));\n}\n",
			fileName, fileSize, fi.ModTime().UnixNano(), byteStreamType, qb)

		// gofmt it
		fset := token.NewFileSet()
		ast, err := parser.ParseFile(fset, "", b.Bytes(), parser.ParseComments)
		if err != nil {
			log.Fatal(err)
		}

		var clean bytes.Buffer
		config := &printer.Config{
			Mode:     printer.TabIndent | printer.UseSpaces,
			Tabwidth: 8,
		}
		err = config.Fprint(&clean, fset, ast)
		if err != nil {
			log.Fatal(err)
		}

		if err := writeFileIfDifferent(embedName, clean.Bytes()); err != nil {
			log.Fatal(err)
		}
	}
}

func writeFileIfDifferent(filename string, contents []byte) error {
	fi, err := os.Stat(filename)
	if err == nil && fi.Size() == int64(len(contents)) && contentsEqual(filename, contents) {
		return nil
	}
	return ioutil.WriteFile(filename, contents, 0644)
}

func contentsEqual(filename string, contents []byte) bool {
	got, err := ioutil.ReadFile(filename)
	if err != nil {
		return false
	}
	return bytes.Equal(got, contents)
}

func compressFile(r io.Reader) ([]byte, int64) {
	var zb bytes.Buffer
	w := zlib.NewWriter(&zb)
	n, err := io.Copy(w, r)
	if err != nil {
		log.Fatal(err)
	}
	w.Close()
	return zb.Bytes(), n
}

func quote(bs []byte) []byte {
	var qb bytes.Buffer
	qb.WriteByte('"')
	run := 0
	for _, b := range bs {
		if b == '\n' {
			qb.WriteString(`\n`)
		}
		if b == '\n' || run > 80 {
			qb.WriteString("\" +\n\t\"")
			run = 0
		}
		if b == '\n' {
			continue
		}
		run++
		if b == '\\' {
			qb.WriteString(`\\`)
			continue
		}
		if b == '"' {
			qb.WriteString(`\"`)
			continue
		}
		if (b >= 32 && b <= 126) || b == '\t' {
			qb.WriteByte(b)
			continue
		}
		fmt.Fprintf(&qb, "\\x%02x", b)
	}
	qb.WriteByte('"')
	return qb.Bytes()
}

func matchingFiles(p *regexp.Regexp) []string {
	var f []string
	d, err := os.Open(".")
	if err != nil {
		log.Fatal(err)
	}
	defer d.Close()
	names, err := d.Readdirnames(-1)
	if err != nil {
		log.Fatal(err)
	}
	for _, n := range names {
		if strings.HasPrefix(n, "zembed_") {
			continue
		}
		if p.MatchString(n) {
			f = append(f, n)
		}
	}
	return f
}

func parseFileEmbed() (pkgName string, filePattern *regexp.Regexp, modTime time.Time, err error) {
	fe, err := os.Open("fileembed.go")
	if err != nil {
		return
	}
	defer fe.Close()

	fi, err := fe.Stat()
	if err != nil {
		return
	}
	modTime = fi.ModTime()

	fs := token.NewFileSet()
	astf, err := parser.ParseFile(fs, "fileembed.go", fe, parser.PackageClauseOnly|parser.ParseComments)
	if err != nil {
		return
	}
	pkgName = astf.Name.Name

	if astf.Doc == nil {
		err = fmt.Errorf("no package comment before the %q line", "package "+pkgName)
		return
	}

	pkgComment := astf.Doc.Text()
	findPattern := regexp.MustCompile(`(?m)^#fileembed\s+pattern\s+(\S+)\s*$`)
	m := findPattern.FindStringSubmatch(pkgComment)
	if m == nil {
		err = fmt.Errorf("package comment lacks line of form: #fileembed pattern <pattern>")
		return
	}
	pattern := m[1]
	filePattern, err = regexp.Compile(pattern)
	if err != nil {
		err = fmt.Errorf("bad regexp %q: %v", pattern, err)
		return
	}
	return
}

// chunksOf takes a (presumably large) file's uncompressed input,
// rolling-checksum splits it into ~514 byte chunks, compresses each,
// base64s each, and writes chunk files out, with each file just
// defining an exported fileembed.Opener variable named C<xxxx> where
// xxxx is the first 8 lowercase hex digits of the SHA-1 of the chunk
// value pre-compression.  The return value is a Go expression
// referencing each of those chunks concatenated together.
func chunksOf(in []byte) (stringExpression []byte) {
	var multiParts [][]byte
	rs := rollsum.New()
	const nBits = 9 // ~512 byte chunks
	last := 0
	for i, b := range in {
		rs.Roll(b)
		if rs.OnSplitWithBits(nBits) || i == len(in)-1 {
			raw := in[last : i+1] // inclusive
			last = i + 1
			s1 := sha1.New()
			s1.Write(raw)
			sha1hex := fmt.Sprintf("%x", s1.Sum(nil))[:8]
			writeChunkFile(sha1hex, raw)
			multiParts = append(multiParts, []byte(fmt.Sprintf("chunkpkg.C%s", sha1hex)))
		}
	}
	return bytes.Join(multiParts, []byte(",\n\t"))
}

func writeChunkFile(hex string, raw []byte) {
	path := os.Getenv("GOPATH")
	if path == "" {
		log.Fatalf("No GOPATH set")
	}
	path = filepath.SplitList(path)[0]
	file := filepath.Join(path, "src", filepath.FromSlash(*chunkPackage), "chunk_"+hex+".go")
	zb, _ := compressFile(bytes.NewReader(raw))
	var buf bytes.Buffer
	buf.WriteString("// THIS FILE IS AUTO-GENERATED. SEE README.\n\n")
	buf.WriteString("package chunkpkg\n")
	buf.WriteString("import \"" + *fileEmbedPkgPath + "\"\n\n")
	fmt.Fprintf(&buf, "var C%s fileembed.Opener\n\nfunc init() { C%s = fileembed.ZlibCompressedBase64(%s)\n }\n",
		hex,
		hex,
		quote([]byte(base64.StdEncoding.EncodeToString(zb))))
	err := writeFileIfDifferent(file, buf.Bytes())
	if err != nil {
		log.Fatalf("Error writing chunk %s to %v: %v", hex, file, err)
	}
}
<commit_msg>genfileembed: touch the file modtime, even if contents are same, but it's old<commit_after>/*
Copyright 2012 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// The genfileembed command embeds resources into Go files, to eliminate run-time
// dependencies on files on the filesystem.
package main

import (
	"bytes"
	"compress/zlib"
	"crypto/sha1"
	"encoding/base64"
	"flag"
	"fmt"
	"go/parser"
	"go/printer"
	"go/token"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"camlistore.org/pkg/rollsum"
)

var (
	processAll = flag.Bool("all", false, "process all files (if false, only process modified files)")

	fileEmbedPkgPath = flag.String("fileembed-package", "camlistore.org/pkg/fileembed", "the Go package name for fileembed. If you have vendored fileembed (e.g. with goven), you can use this flag to ensure that generated code imports the vendored package.")

	chunkThreshold = flag.Int64("chunk-threshold", 0, "If non-zero, the maximum size of a file before it's cut up into content-addressable chunks with a rolling checksum")
	chunkPackage   = flag.String("chunk-package", "", "Package to hold chunks")
)

const (
	maxUncompressed = 50 << 10 // 50KB
	// Threshold ratio for compression.
	// Files which don't compress at least as well are kept uncompressed.
	zRatio = 0.5
)

func usage() {
	fmt.Fprintf(os.Stderr, "usage: genfileembed [flags] [<dir>]\n")
	flag.PrintDefaults()
	os.Exit(2)
}

func main() {
	flag.Usage = usage
	flag.Parse()

	dir := "."
	switch flag.NArg() {
	case 0:
	case 1:
		dir = flag.Arg(0)
		if err := os.Chdir(dir); err != nil {
			log.Fatalf("chdir(%q) = %v", dir, err)
		}
	default:
		flag.Usage()
	}

	pkgName, filePattern, fileEmbedModTime, err := parseFileEmbed()
	if err != nil {
		log.Fatalf("Error parsing %s/fileembed.go: %v", dir, err)
	}

	for _, fileName := range matchingFiles(filePattern) {
		fi, err := os.Stat(fileName)
		if err != nil {
			log.Fatal(err)
		}

		embedName := "zembed_" + fileName + ".go"
		zfi, zerr := os.Stat(embedName)
		genFile := func() bool {
			if *processAll || zerr != nil {
				return true
			}
			if zfi.ModTime().Before(fi.ModTime()) {
				return true
			}
			if zfi.ModTime().Before(fileEmbedModTime) {
				return true
			}
			return false
		}
		if !genFile() {
			continue
		}
		log.Printf("Updating %s (package %s)", filepath.Join(dir, embedName), pkgName)

		bs, err := ioutil.ReadFile(fileName)
		if err != nil {
			log.Fatal(err)
		}

		zb, fileSize := compressFile(bytes.NewReader(bs))
		ratio := float64(len(zb)) / float64(fileSize)
		byteStreamType := ""
		var qb []byte // quoted string, or Go expression evaluating to a string
		var imports string
		if *chunkThreshold > 0 && int64(len(bs)) > *chunkThreshold {
			byteStreamType = "fileembed.Multi"
			qb = chunksOf(bs)
			if *chunkPackage == "" {
				log.Fatalf("Must provide a --chunk-package value with --chunk-threshold")
			}
			imports = fmt.Sprintf("import chunkpkg \"%s\"\n", *chunkPackage)
		} else if fileSize < maxUncompressed || ratio > zRatio {
			byteStreamType = "fileembed.String"
			qb = quote(bs)
		} else {
			byteStreamType = "fileembed.ZlibCompressedBase64"
			qb = quote([]byte(base64.StdEncoding.EncodeToString(zb)))
		}

		var b bytes.Buffer
		fmt.Fprintf(&b, "// THIS FILE IS AUTO-GENERATED FROM %s\n", fileName)
		fmt.Fprintf(&b, "// DO NOT EDIT.\n\n")
		fmt.Fprintf(&b, "package %s\n\n", pkgName)
		fmt.Fprintf(&b, "import \"time\"\n\n")
		fmt.Fprintf(&b, "import \""+*fileEmbedPkgPath+"\"\n\n")
		b.WriteString(imports)
		fmt.Fprintf(&b, "func init() {\n\tFiles.Add(%q, %d, time.Unix(0, %d), %s(%s));\n}\n",
			fileName, fileSize, fi.ModTime().UnixNano(), byteStreamType, qb)

		// gofmt it
		fset := token.NewFileSet()
		ast, err := parser.ParseFile(fset, "", b.Bytes(), parser.ParseComments)
		if err != nil {
			log.Fatal(err)
		}

		var clean bytes.Buffer
		config := &printer.Config{
			Mode:     printer.TabIndent | printer.UseSpaces,
			Tabwidth: 8,
		}
		err = config.Fprint(&clean, fset, ast)
		if err != nil {
			log.Fatal(err)
		}

		if err := writeFileIfDifferent(embedName, clean.Bytes()); err != nil {
			log.Fatal(err)
		}
	}
}

func writeFileIfDifferent(filename string, contents []byte) error {
	fi, err := os.Stat(filename)
	if err == nil && fi.Size() == int64(len(contents)) && contentsEqual(filename, contents) {
		os.Chtimes(filename, time.Now(), time.Now())
		return nil
	}
	return ioutil.WriteFile(filename, contents, 0644)
}

func contentsEqual(filename string, contents []byte) bool {
	got, err := ioutil.ReadFile(filename)
	if err != nil {
		return false
	}
	return bytes.Equal(got, contents)
}

func compressFile(r io.Reader) ([]byte, int64) {
	var zb bytes.Buffer
	w := zlib.NewWriter(&zb)
	n, err := io.Copy(w, r)
	if err != nil {
		log.Fatal(err)
	}
	w.Close()
	return zb.Bytes(), n
}

func quote(bs []byte) []byte {
	var qb bytes.Buffer
	qb.WriteByte('"')
	run := 0
	for _, b := range bs {
		if b == '\n' {
			qb.WriteString(`\n`)
		}
		if b == '\n' || run > 80 {
			qb.WriteString("\" +\n\t\"")
			run = 0
		}
		if b == '\n' {
			continue
		}
		run++
		if b == '\\' {
			qb.WriteString(`\\`)
			continue
		}
		if b == '"' {
			qb.WriteString(`\"`)
			continue
		}
		if (b >= 32 && b <= 126) || b == '\t' {
			qb.WriteByte(b)
			continue
		}
		fmt.Fprintf(&qb, "\\x%02x", b)
	}
	qb.WriteByte('"')
	return qb.Bytes()
}

func matchingFiles(p *regexp.Regexp) []string {
	var f []string
	d, err := os.Open(".")
	if err != nil {
		log.Fatal(err)
	}
	defer d.Close()
	names, err := d.Readdirnames(-1)
	if err != nil {
		log.Fatal(err)
	}
	for _, n := range names {
		if strings.HasPrefix(n, "zembed_") {
			continue
		}
		if p.MatchString(n) {
			f = append(f, n)
		}
	}
	return f
}

func parseFileEmbed() (pkgName string, filePattern *regexp.Regexp, modTime time.Time, err error) {
	fe, err := os.Open("fileembed.go")
	if err != nil {
		return
	}
	defer fe.Close()

	fi, err := fe.Stat()
	if err != nil {
		return
	}
	modTime = fi.ModTime()

	fs := token.NewFileSet()
	astf, err := parser.ParseFile(fs, "fileembed.go", fe, parser.PackageClauseOnly|parser.ParseComments)
	if err != nil {
		return
	}
	pkgName = astf.Name.Name

	if astf.Doc == nil {
		err = fmt.Errorf("no package comment before the %q line", "package "+pkgName)
		return
	}

	pkgComment := astf.Doc.Text()
	findPattern := regexp.MustCompile(`(?m)^#fileembed\s+pattern\s+(\S+)\s*$`)
	m := findPattern.FindStringSubmatch(pkgComment)
	if m == nil {
		err = fmt.Errorf("package comment lacks line of form: #fileembed pattern <pattern>")
		return
	}
	pattern := m[1]
	filePattern, err = regexp.Compile(pattern)
	if err != nil {
		err = fmt.Errorf("bad regexp %q: %v", pattern, err)
		return
	}
	return
}

// chunksOf takes a (presumably large) file's uncompressed input,
// rolling-checksum splits it into ~514 byte chunks, compresses each,
// base64s each, and writes chunk files out, with each file just
// defining an exported fileembed.Opener variable named C<xxxx> where
// xxxx is the first 8 lowercase hex digits of the SHA-1 of the chunk
// value pre-compression.  The return value is a Go expression
// referencing each of those chunks concatenated together.
func chunksOf(in []byte) (stringExpression []byte) {
	var multiParts [][]byte
	rs := rollsum.New()
	const nBits = 9 // ~512 byte chunks
	last := 0
	for i, b := range in {
		rs.Roll(b)
		if rs.OnSplitWithBits(nBits) || i == len(in)-1 {
			raw := in[last : i+1] // inclusive
			last = i + 1
			s1 := sha1.New()
			s1.Write(raw)
			sha1hex := fmt.Sprintf("%x", s1.Sum(nil))[:8]
			writeChunkFile(sha1hex, raw)
			multiParts = append(multiParts, []byte(fmt.Sprintf("chunkpkg.C%s", sha1hex)))
		}
	}
	return bytes.Join(multiParts, []byte(",\n\t"))
}

func writeChunkFile(hex string, raw []byte) {
	path := os.Getenv("GOPATH")
	if path == "" {
		log.Fatalf("No GOPATH set")
	}
	path = filepath.SplitList(path)[0]
	file := filepath.Join(path, "src", filepath.FromSlash(*chunkPackage), "chunk_"+hex+".go")
	zb, _ := compressFile(bytes.NewReader(raw))
	var buf bytes.Buffer
	buf.WriteString("// THIS FILE IS AUTO-GENERATED. SEE README.\n\n")
	buf.WriteString("package chunkpkg\n")
	buf.WriteString("import \"" + *fileEmbedPkgPath + "\"\n\n")
	fmt.Fprintf(&buf, "var C%s fileembed.Opener\n\nfunc init() { C%s = fileembed.ZlibCompressedBase64(%s)\n }\n",
		hex,
		hex,
		quote([]byte(base64.StdEncoding.EncodeToString(zb))))
	err := writeFileIfDifferent(file, buf.Bytes())
	if err != nil {
		log.Fatalf("Error writing chunk %s to %v: %v", hex, file, err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2019 ETH Zurich, Anapaya Systems
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package rpc implements SCION Infra RPC calls over QUIC.
package rpc

import (
	"context"
	"crypto/tls"
	"fmt"
	"io"
	mrand "math/rand"
	"net"
	"strings"
	"sync"
	"time"

	quic "github.com/lucas-clemente/quic-go"
	capnp "zombiezen.com/go/capnproto2"

	"github.com/scionproto/scion/go/lib/log"
	"github.com/scionproto/scion/go/lib/serrors"
	"github.com/scionproto/scion/go/lib/snet"
	"github.com/scionproto/scion/go/proto"
)

const (
	// CtxTimedOutError is a custom QUIC error code that is used when canceling
	// writes due to context expiration.
	CtxTimedOutError = iota + 1

	errorNoError quic.ErrorCode = 0x100
)

// Server is the configuration for a QUIC RPC server. Messages are SCION Infra
// Signed Control Payloads. For each accepted connection, the server parses the
// message from the client and passes it to the handler.
type Server struct {
	// Conn is the connection to listen on. It can be shared with Clients,
	// because QUIC connection IDs are used to demux the packets.
	Conn net.PacketConn
	// TLSConfig is the server's TLS configuration for starting QUIC connections.
	TLSConfig *tls.Config
	// QUICConfig is the server's QUIC configuration.
	QUICConfig *quic.Config
	// Handler is called for every RPC Request receivd by the server.
	Handler Handler

	mu sync.Mutex
	// listener is the conn to accept connections on.
	listener quic.Listener
}

func (s *Server) ListenAndServe() error {
	if err := s.initListener(); err != nil {
		return err
	}
	for {
		session, err := s.listener.Accept(context.Background())
		if err != nil {
			if strings.Contains(err.Error(), "server closed") {
				return err
			}
			log.Warn("[quic] server accept error", "err", err)
			continue
		}
		go func() {
			defer log.HandlePanic()
			if err := s.handleQUICSession(session); err != nil {
				log.Warn("[quic] server handler exited with error", "err", err)
			}
		}()
	}
}

func (s *Server) initListener() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.listener != nil {
		return serrors.New("cannot listen on same server twice")
	}
	listener, err := quic.Listen(s.Conn, s.TLSConfig, s.QUICConfig)
	if err != nil {
		return err
	}
	s.listener = listener
	return nil
}

// Close closes the Server's listener. All active QUIC connections are
// immediately torn down. It is safe to call close multiple times.
func (s *Server) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.listener == nil {
		// Close on non-listening server is a no-op
		return nil
	}
	return s.listener.Close()
}

func (s *Server) handleQUICSession(session quic.Session) error {
	stream, err := session.AcceptStream(context.Background())
	if err != nil {
		return err
	}
	msg, err := proto.SafeDecode(capnp.NewDecoder(stream))
	if err != nil {
		return err
	}
	rw := &replyWriter{stream: stream}
	request := &Request{
		Message: msg,
		Address: session.RemoteAddr(),
	}
	s.Handler.ServeRPC(rw, request)
	return nil
}

type Client struct {
	// Conn is the connection to initiate QUIC Sessions on. It can be shared
	// with Servers, because QUIC connection IDs are used to demux the packets.
	Conn net.PacketConn
	// TLSConfig is the client's TLS configuration for starting QUIC connections.
	TLSConfig *tls.Config
	// QUICConfig is the client's QUIC configuration.
	QUICConfig *quic.Config
}

// Request sends the request to the host described by address, and blocks until
// a reply is received (or the context times out). If a reply is received, it
// is returned.
func (c *Client) Request(ctx context.Context, request *Request, address net.Addr) (*Reply, error) {
	addressStr := computeAddressStr(address)

	var session quic.Session
	for sleep := 2 * time.Millisecond; ctx.Err() == nil; sleep = sleep * 2 {
		var err error
		session, err = quic.DialContext(ctx, c.Conn, address, addressStr, c.TLSConfig, c.QUICConfig)
		if err == nil {
			break
		}
		// Unfortunately there is no better way to check the error.
		// https://github.com/lucas-clemente/quic-go/issues/2441
		if err.Error() != "SERVER_BUSY" {
			return nil, err
		}
		select {
		case <-time.After(sleep + time.Duration(mrand.Int()%5000)*time.Microsecond):
		case <-ctx.Done():
		}
	}

	stream, err := session.OpenStream()
	if err != nil {
		return nil, err
	}
	go func() {
		defer log.HandlePanic()
		<-ctx.Done()
		stream.CancelRead(CtxTimedOutError)
		stream.CancelWrite(CtxTimedOutError)
	}()

	err = capnp.NewEncoder(stream).Encode(request.Message)
	if err != nil {
		return nil, err
	}
	msg, err := proto.SafeDecode(capnp.NewDecoder(stream))
	if err != nil {
		// if we have a timeout make it visible.
		if strings.Contains(err.Error(),
			fmt.Sprintf("canceled with error code %d", CtxTimedOutError)) && ctx.Err() != nil {
			return nil, ctx.Err()
		}
		return nil, err
	}

	if err := stream.Close(); err != nil {
		return nil, err
	}
	if err := session.CloseWithError(errorNoError, ""); err != nil {
		return nil, err
	}
	return &Reply{Message: msg}, nil
}

func (c *Client) sendRequest() error {
	return nil
}

// computeAddressStr returns a parseable version of the SCION address for use
// with QUIC SNI.
func computeAddressStr(address net.Addr) string {
	if v, ok := address.(*snet.UDPAddr); ok {
		return fmt.Sprintf("[%s]:%d", v.Host.IP, v.Host.Port)
	}
	return address.String()
}

// Handler is called by RPC servers whenever a new request arrives.
// Implementations should write replies to rw.
type Handler interface {
	ServeRPC(rw ReplyWriter, request *Request)
}

// ReplyWriter provides handlers a way to respond to requests. ReplyWriter
// keeps a connection alive for replying. Method WriteReply can block; to
// unblock the method (and to close the connection ahead of time), call
// Close.  ReplyWriter implementations must also close the connection whenever
// they return from WriteReply.
type ReplyWriter interface {
	// WriteReply blocks until the Reply is sent back to the peer. The
	// underlying connection is always closed before WriteReply returns.
	WriteReply(*Reply) error
	// Close closes any connections kept open by this writer, and unblocks an
	// ongoing WriteReply. It is safe to call Close concurrently with
	// WriteReply. Close can be safely called multiple times.
	io.Closer
}

type replyWriter struct {
	stream quic.Stream
}

func (rw *replyWriter) WriteReply(reply *Reply) error {
	if err := capnp.NewEncoder(rw.stream).Encode(reply.Message); err != nil {
		return err
	}
	if err := rw.stream.Close(); err != nil {
		return err
	}
	return nil
}

func (rw *replyWriter) Close() error {
	return rw.stream.Close()
}
<commit_msg>fix nil pointer panic in rpc backoff logic (#3770)<commit_after>// Copyright 2019 ETH Zurich, Anapaya Systems
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package rpc implements SCION Infra RPC calls over QUIC.
package rpc

import (
	"context"
	"crypto/tls"
	"fmt"
	"io"
	mrand "math/rand"
	"net"
	"strings"
	"sync"
	"time"

	quic "github.com/lucas-clemente/quic-go"
	capnp "zombiezen.com/go/capnproto2"

	"github.com/scionproto/scion/go/lib/log"
	"github.com/scionproto/scion/go/lib/serrors"
	"github.com/scionproto/scion/go/lib/snet"
	"github.com/scionproto/scion/go/proto"
)

const (
	// CtxTimedOutError is a custom QUIC error code that is used when canceling
	// writes due to context expiration.
	CtxTimedOutError = iota + 1

	errorNoError quic.ErrorCode = 0x100
)

// Server is the configuration for a QUIC RPC server. Messages are SCION Infra
// Signed Control Payloads. For each accepted connection, the server parses the
// message from the client and passes it to the handler.
type Server struct {
	// Conn is the connection to listen on. It can be shared with Clients,
	// because QUIC connection IDs are used to demux the packets.
	Conn net.PacketConn
	// TLSConfig is the server's TLS configuration for starting QUIC connections.
	TLSConfig *tls.Config
	// QUICConfig is the server's QUIC configuration.
	QUICConfig *quic.Config
	// Handler is called for every RPC Request receivd by the server.
	Handler Handler

	mu sync.Mutex
	// listener is the conn to accept connections on.
	listener quic.Listener
}

func (s *Server) ListenAndServe() error {
	if err := s.initListener(); err != nil {
		return err
	}
	for {
		session, err := s.listener.Accept(context.Background())
		if err != nil {
			if strings.Contains(err.Error(), "server closed") {
				return err
			}
			log.Warn("[quic] server accept error", "err", err)
			continue
		}
		go func() {
			defer log.HandlePanic()
			if err := s.handleQUICSession(session); err != nil {
				log.Warn("[quic] server handler exited with error", "err", err)
			}
		}()
	}
}

func (s *Server) initListener() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.listener != nil {
		return serrors.New("cannot listen on same server twice")
	}
	listener, err := quic.Listen(s.Conn, s.TLSConfig, s.QUICConfig)
	if err != nil {
		return err
	}
	s.listener = listener
	return nil
}

// Close closes the Server's listener. All active QUIC connections are
// immediately torn down. It is safe to call close multiple times.
func (s *Server) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.listener == nil {
		// Close on non-listening server is a no-op
		return nil
	}
	return s.listener.Close()
}

func (s *Server) handleQUICSession(session quic.Session) error {
	stream, err := session.AcceptStream(context.Background())
	if err != nil {
		return err
	}
	msg, err := proto.SafeDecode(capnp.NewDecoder(stream))
	if err != nil {
		return err
	}
	rw := &replyWriter{stream: stream}
	request := &Request{
		Message: msg,
		Address: session.RemoteAddr(),
	}
	s.Handler.ServeRPC(rw, request)
	return nil
}

type Client struct {
	// Conn is the connection to initiate QUIC Sessions on. It can be shared
	// with Servers, because QUIC connection IDs are used to demux the packets.
	Conn net.PacketConn
	// TLSConfig is the client's TLS configuration for starting QUIC connections.
	TLSConfig *tls.Config
	// QUICConfig is the client's QUIC configuration.
	QUICConfig *quic.Config
}

// Request sends the request to the host described by address, and blocks until
// a reply is received (or the context times out). If a reply is received, it
// is returned.
func (c *Client) Request(ctx context.Context, request *Request, address net.Addr) (*Reply, error) {
	addressStr := computeAddressStr(address)

	var session quic.Session
	for sleep := 2 * time.Millisecond; ctx.Err() == nil; sleep = sleep * 2 {
		var err error
		session, err = quic.DialContext(ctx, c.Conn, address, addressStr, c.TLSConfig, c.QUICConfig)
		if err == nil {
			break
		}
		// Unfortunately there is no better way to check the error.
		// https://github.com/lucas-clemente/quic-go/issues/2441
		if err.Error() != "SERVER_BUSY" {
			return nil, err
		}
		select {
		case <-time.After(sleep + time.Duration(mrand.Int()%5000)*time.Microsecond):
		case <-ctx.Done():
		}
	}
	if ctx.Err() != nil {
		return nil, ctx.Err()
	}

	stream, err := session.OpenStream()
	if err != nil {
		return nil, err
	}
	go func() {
		defer log.HandlePanic()
		<-ctx.Done()
		stream.CancelRead(CtxTimedOutError)
		stream.CancelWrite(CtxTimedOutError)
	}()

	err = capnp.NewEncoder(stream).Encode(request.Message)
	if err != nil {
		return nil, err
	}
	msg, err := proto.SafeDecode(capnp.NewDecoder(stream))
	if err != nil {
		// if we have a timeout make it visible.
		if strings.Contains(err.Error(),
			fmt.Sprintf("canceled with error code %d", CtxTimedOutError)) && ctx.Err() != nil {
			return nil, ctx.Err()
		}
		return nil, err
	}

	if err := stream.Close(); err != nil {
		return nil, err
	}
	if err := session.CloseWithError(errorNoError, ""); err != nil {
		return nil, err
	}
	return &Reply{Message: msg}, nil
}

func (c *Client) sendRequest() error {
	return nil
}

// computeAddressStr returns a parseable version of the SCION address for use
// with QUIC SNI.
func computeAddressStr(address net.Addr) string {
	if v, ok := address.(*snet.UDPAddr); ok {
		return fmt.Sprintf("[%s]:%d", v.Host.IP, v.Host.Port)
	}
	return address.String()
}

// Handler is called by RPC servers whenever a new request arrives.
// Implementations should write replies to rw.
type Handler interface {
	ServeRPC(rw ReplyWriter, request *Request)
}

// ReplyWriter provides handlers a way to respond to requests. ReplyWriter
// keeps a connection alive for replying. Method WriteReply can block; to
// unblock the method (and to close the connection ahead of time), call
// Close.  ReplyWriter implementations must also close the connection whenever
// they return from WriteReply.
type ReplyWriter interface {
	// WriteReply blocks until the Reply is sent back to the peer. The
	// underlying connection is always closed before WriteReply returns.
	WriteReply(*Reply) error
	// Close closes any connections kept open by this writer, and unblocks an
	// ongoing WriteReply. It is safe to call Close concurrently with
	// WriteReply. Close can be safely called multiple times.
	io.Closer
}

type replyWriter struct {
	stream quic.Stream
}

func (rw *replyWriter) WriteReply(reply *Reply) error {
	if err := capnp.NewEncoder(rw.stream).Encode(reply.Message); err != nil {
		return err
	}
	if err := rw.stream.Close(); err != nil {
		return err
	}
	return nil
}

func (rw *replyWriter) Close() error {
	return rw.stream.Close()
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsCognitoIdentityProvider() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsCognitoIdentityProviderCreate,
		Read:   resourceAwsCognitoIdentityProviderRead,
		Update: resourceAwsCognitoIdentityProviderUpdate,
		Delete: resourceAwsCognitoIdentityProviderDelete,

		Timeouts: &schema.ResourceTimeout{
			Delete: schema.DefaultTimeout(5 * time.Minute),
		},

		Schema: map[string]*schema.Schema{
			"attribute_mapping": {
				Type:     schema.TypeMap,
				Optional: true,
			},

			"idp_identifiers": {
				Type:     schema.TypeList,
				Optional: true,
				Elem: &schema.Schema{
					Type: schema.TypeString,
				},
			},

			"provider_details": {
				Type:     schema.TypeMap,
				Optional: true,
				Computed: true,
			},

			"provider_name": {
				Type:     schema.TypeString,
				Required: true,
			},

			"provider_type": {
				Type:     schema.TypeString,
				Required: true,
			},

			"user_pool_id": {
				Type:     schema.TypeString,
				Required: true,
			},
		},
	}
}

func resourceAwsCognitoIdentityProviderCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cognitoidpconn
	log.Print("[DEBUG] Creating Cognito Identity Provider")

	name := aws.String(d.Get("provider_name").(string))
	params := &cognitoidentityprovider.CreateIdentityProviderInput{
		ProviderName: name,
		ProviderType: aws.String(d.Get("provider_type").(string)),
		UserPoolId:   aws.String(d.Get("user_pool_id").(string)),
	}

	if v, ok := d.GetOk("attribute_mapping"); ok {
		params.AttributeMapping = expandCognitoIdentityProviderMap(v.(map[string]interface{}))
	}

	if v, ok := d.GetOk("provider_details"); ok {
		params.ProviderDetails = expandCognitoIdentityProviderMap(v.(map[string]interface{}))
	}

	if v, ok := d.GetOk("idp_identifiers"); ok {
		params.IdpIdentifiers = expandStringList(v.([]interface{}))
	}

	_, err := conn.CreateIdentityProvider(params)
	if err != nil {
		return fmt.Errorf("Error creating Cognito Identity Provider: %s", err)
	}

	d.SetId(*name)

	return resourceAwsCognitoIdentityProviderRead(d, meta)
}

func resourceAwsCognitoIdentityProviderRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cognitoidpconn
	log.Printf("[DEBUG] Reading Cognito Identity Provider: %s", d.Id())

	ret, err := conn.DescribeIdentityProvider(&cognitoidentityprovider.DescribeIdentityProviderInput{
		ProviderName: aws.String(d.Id()),
		UserPoolId:   aws.String(d.Get("user_pool_id").(string)),
	})

	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ResourceNotFoundException" {
			d.SetId("")
			return nil
		}
		return err
	}

	ip := ret.IdentityProvider
	d.Set("provider_name", ip.ProviderName)
	d.Set("provider_type", ip.ProviderType)
	d.Set("user_pool_id", ip.UserPoolId)

	if err := d.Set("attribute_mapping", flattenCognitoIdentityProviderMap(ip.AttributeMapping)); err != nil {
		return fmt.Errorf("[DEBUG] Error setting attribute_mapping error: %#v", err)
	}

	if err := d.Set("provider_details", flattenCognitoIdentityProviderMap(ip.ProviderDetails)); err != nil {
		return fmt.Errorf("[DEBUG] Error setting provider_details error: %#v", err)
	}

	if err := d.Set("idp_identifiers", flattenStringList(ip.IdpIdentifiers)); err != nil {
		return fmt.Errorf("[DEBUG] Error setting idp_identifiers error: %#v", err)
	}

	return nil
}

func resourceAwsCognitoIdentityProviderUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cognitoidpconn
	log.Print("[DEBUG] Updating Cognito Identity Provider")

	params := &cognitoidentityprovider.UpdateIdentityProviderInput{
		UserPoolId:   aws.String(d.Get("UserPoolId").(string)),
		ProviderName: aws.String(d.Id()),
	}

	if d.HasChange("attribute_mapping") {
		params.AttributeMapping = expandCognitoIdentityProviderMap(d.Get("attribute_mapping").(map[string]interface{}))
	}

	if d.HasChange("provider_details") {
		params.ProviderDetails = expandCognitoIdentityProviderMap(d.Get("provider_details").(map[string]interface{}))
	}

	if d.HasChange("idp_identifiers") {
		params.IdpIdentifiers = expandStringList(d.Get("supported_login_providers").([]interface{}))
	}

	_, err := conn.UpdateIdentityProvider(params)
	if err != nil {
		return fmt.Errorf("Error updating Cognito Identity Provider: %s", err)
	}

	return resourceAwsCognitoIdentityProviderRead(d, meta)
}

func resourceAwsCognitoIdentityProviderDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cognitoidpconn
	log.Printf("[DEBUG] Deleting Cognito Identity Provider: %s", d.Id())

	return resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError {
		_, err := conn.DeleteIdentityProvider(&cognitoidentityprovider.DeleteIdentityProviderInput{
			ProviderName: aws.String(d.Id()),
			UserPoolId:   aws.String(d.Get("user_pool_id").(string)),
		})

		if err == nil {
			d.SetId("")
			return nil
		}

		return resource.NonRetryableError(err)
	})
}
<commit_msg>Accoring to ldenman comment: fix Get with wrong variable name, add ForceNew to user_pool_id<commit_after>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsCognitoIdentityProvider() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsCognitoIdentityProviderCreate,
		Read:   resourceAwsCognitoIdentityProviderRead,
		Update: resourceAwsCognitoIdentityProviderUpdate,
		Delete: resourceAwsCognitoIdentityProviderDelete,

		Timeouts: &schema.ResourceTimeout{
			Delete: schema.DefaultTimeout(5 * time.Minute),
		},

		Schema: map[string]*schema.Schema{
			"attribute_mapping": {
				Type:     schema.TypeMap,
				Optional: true,
			},

			"idp_identifiers": {
				Type:     schema.TypeList,
				Optional: true,
				Elem: &schema.Schema{
					Type: schema.TypeString,
				},
			},

			"provider_details": {
				Type:     schema.TypeMap,
				Optional: true,
				Computed: true,
			},

			"provider_name": {
				Type:     schema.TypeString,
				Required: true,
			},

			"provider_type": {
				Type:     schema.TypeString,
				Required: true,
			},

			"user_pool_id": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
		},
	}
}

func resourceAwsCognitoIdentityProviderCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cognitoidpconn
	log.Print("[DEBUG] Creating Cognito Identity Provider")

	name := aws.String(d.Get("provider_name").(string))
	params := &cognitoidentityprovider.CreateIdentityProviderInput{
		ProviderName: name,
		ProviderType: aws.String(d.Get("provider_type").(string)),
		UserPoolId:   aws.String(d.Get("user_pool_id").(string)),
	}

	if v, ok := d.GetOk("attribute_mapping"); ok {
		params.AttributeMapping = expandCognitoIdentityProviderMap(v.(map[string]interface{}))
	}

	if v, ok := d.GetOk("provider_details"); ok {
		params.ProviderDetails = expandCognitoIdentityProviderMap(v.(map[string]interface{}))
	}

	if v, ok := d.GetOk("idp_identifiers"); ok {
		params.IdpIdentifiers = expandStringList(v.([]interface{}))
	}

	_, err := conn.CreateIdentityProvider(params)
	if err != nil {
		return fmt.Errorf("Error creating Cognito Identity Provider: %s", err)
	}

	d.SetId(*name)

	return resourceAwsCognitoIdentityProviderRead(d, meta)
}

func resourceAwsCognitoIdentityProviderRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cognitoidpconn
	log.Printf("[DEBUG] Reading Cognito Identity Provider: %s", d.Id())

	ret, err := conn.DescribeIdentityProvider(&cognitoidentityprovider.DescribeIdentityProviderInput{
		ProviderName: aws.String(d.Id()),
		UserPoolId:   aws.String(d.Get("user_pool_id").(string)),
	})

	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ResourceNotFoundException" {
			d.SetId("")
			return nil
		}
		return err
	}

	ip := ret.IdentityProvider
	d.Set("provider_name", ip.ProviderName)
	d.Set("provider_type", ip.ProviderType)
	d.Set("user_pool_id", ip.UserPoolId)

	if err := d.Set("attribute_mapping", flattenCognitoIdentityProviderMap(ip.AttributeMapping)); err != nil {
		return fmt.Errorf("[DEBUG] Error setting attribute_mapping error: %#v", err)
	}

	if err := d.Set("provider_details", flattenCognitoIdentityProviderMap(ip.ProviderDetails)); err != nil {
		return fmt.Errorf("[DEBUG] Error setting provider_details error: %#v", err)
	}

	if err := d.Set("idp_identifiers", flattenStringList(ip.IdpIdentifiers)); err != nil {
		return fmt.Errorf("[DEBUG] Error setting idp_identifiers error: %#v", err)
	}

	return nil
}

func resourceAwsCognitoIdentityProviderUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cognitoidpconn
	log.Print("[DEBUG] Updating Cognito Identity Provider")

	params := &cognitoidentityprovider.UpdateIdentityProviderInput{
		UserPoolId:   aws.String(d.Get("user_pool_id").(string)),
		ProviderName: aws.String(d.Id()),
	}

	if d.HasChange("attribute_mapping") {
		params.AttributeMapping = expandCognitoIdentityProviderMap(d.Get("attribute_mapping").(map[string]interface{}))
	}

	if d.HasChange("provider_details") {
		params.ProviderDetails = expandCognitoIdentityProviderMap(d.Get("provider_details").(map[string]interface{}))
	}

	if d.HasChange("idp_identifiers") {
		params.IdpIdentifiers = expandStringList(d.Get("supported_login_providers").([]interface{}))
	}

	_, err := conn.UpdateIdentityProvider(params)
	if err != nil {
		return fmt.Errorf("Error updating Cognito Identity Provider: %s", err)
	}

	return resourceAwsCognitoIdentityProviderRead(d, meta)
}

func resourceAwsCognitoIdentityProviderDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cognitoidpconn
	log.Printf("[DEBUG] Deleting Cognito Identity Provider: %s", d.Id())

	return resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError {
		_, err := conn.DeleteIdentityProvider(&cognitoidentityprovider.DeleteIdentityProviderInput{
			ProviderName: aws.String(d.Id()),
			UserPoolId:   aws.String(d.Get("user_pool_id").(string)),
		})

		if err == nil {
			d.SetId("")
			return nil
		}

		return resource.NonRetryableError(err)
	})
}
<|endoftext|>