diff --git a/ICCC/Data2Message.go b/ICCC/Data2Message.go index 58b7785..57c525d 100644 --- a/ICCC/Data2Message.go +++ b/ICCC/Data2Message.go @@ -1,11 +1,15 @@ package ICCC import ( + "bytes" + "encoding/base64" + "encoding/binary" "fmt" "github.com/SommerEngineering/Ocean/Log" LM "github.com/SommerEngineering/Ocean/Log/Meta" + "net/url" "reflect" - "strconv" + "strings" ) // Function to convert the HTTP data back to a message. @@ -49,37 +53,131 @@ func Data2Message(target interface{}, data map[string][]string) (channel, comman // Use the order of the destination type's fields: for i := 0; i < element.NumField(); i++ { - field := element.Field(i) - switch field.Kind().String() { + // Read the current field: + field := element.Field(i) + + // Choose the right type for this field: + switch field.Kind().String() { case `int64`: + // The name of the field: mapName := fmt.Sprintf(`int:%s`, elementType.Field(i).Name) + + // The value of the field as string: mapValue := data[mapName][0] - v, _ := strconv.ParseInt(mapValue, 10, 64) - field.SetInt(v) + + // The value of the field as bytes: + bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue) + + if errBase64 != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error()) + } else { + // The destination: + var destination int64 + + // A reader for the bytes: + buffer := bytes.NewReader(bytesArray) + + // Try to decode the bytes to an instance of the type: + errBinary := binary.Read(buffer, binary.LittleEndian, &destination) + if errBinary != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the binary data to the type of the ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBinary.Error()) + } else { + // Finnaly, store the value in the message: + field.SetInt(destination) + } + } case `string`: + // The name of the field: mapName := fmt.Sprintf(`str:%s`, elementType.Field(i).Name) + + // The value of the field as string: mapValue := data[mapName][0] - field.SetString(mapValue) + + // The value of the field as bytes: + bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue) + + if errBase64 != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error()) + } else { + // Decode the bytes as string: + text := string(bytesArray) + + // Decode the URL encoded string: + textFinal, errURL := url.QueryUnescape(text) + if errURL != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode a URL encoded string.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errURL.Error()) + } else { + field.SetString(textFinal) + } + } case `float64`: + // The name of the field: mapName := fmt.Sprintf(`f64:%s`, elementType.Field(i).Name) + + // The value of the field as string: mapValue := data[mapName][0] - v, _ := strconv.ParseFloat(mapValue, 64) - field.SetFloat(v) + + // The value of the field as bytes: + bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue) + + if errBase64 != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error()) + } else { + // The destination: + var destination float64 + + // A reader for the bytes: + buffer := bytes.NewReader(bytesArray) + + // Try to decode the bytes to an instance of the type: + errBinary := binary.Read(buffer, binary.LittleEndian, &destination) + if errBinary != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the binary data to the type of the ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBinary.Error()) + } else { + // Finnaly, store the value in the message: + field.SetFloat(destination) + } + } case `bool`: + // The name of the field: mapName := fmt.Sprintf(`bool:%s`, elementType.Field(i).Name) + + // The value of the field as string: mapValue := data[mapName][0] - v, _ := strconv.ParseBool(mapValue) - field.SetBool(v) + + // The value of the field as bytes: + bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue) + if errBase64 != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error()) + } else { + // Store the value: + if bytesArray[0] == 0x1 { + field.SetBool(true) + } else { + field.SetBool(false) + } + } case `uint8`: + // The name of the field: mapName := fmt.Sprintf(`ui8:%s`, elementType.Field(i).Name) + + // The value of the field as string: mapValue := data[mapName][0] - v, _ := strconv.ParseUint(mapValue, 16, 8) - field.SetUint(v) + + // The value of the field as bytes: + bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue) + + if errBase64 != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error()) + } else { + // Store the value: + field.SetUint(uint64(bytesArray[0])) + } // Case: Arrays... case `slice`: @@ -87,63 +185,146 @@ func Data2Message(target interface{}, data map[string][]string) (channel, comman sliceKind := reflect.ValueOf(sliceInterface).Type().String() switch sliceKind { - case `[]uint8`: // bytes + case `[]uint8`: // a byte array + // The name of the field: mapName := fmt.Sprintf(`ui8[]:%s`, elementType.Field(i).Name) - mapValues := data[mapName] - fieldLen := len(mapValues) - fieldData := make([]uint8, fieldLen, fieldLen) - for n, mapValue := range mapValues { - v, _ := strconv.ParseUint(mapValue, 16, 8) - fieldData[n] = byte(v) - } - fieldDataValue := reflect.ValueOf(fieldData) - field.Set(fieldDataValue) + // The value of the field as string: + mapValue := data[mapName][0] + + // The value of the field as bytes: + bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue) + + if errBase64 != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error()) + } else { + // Store the values in the message: + fieldDataValue := reflect.ValueOf(bytesArray) + field.Set(fieldDataValue) + } case `[]int64`: + // The name of the field: mapName := fmt.Sprintf(`int[]:%s`, elementType.Field(i).Name) - mapValues := data[mapName] - fieldLen := len(mapValues) - fieldData := make([]int64, fieldLen, fieldLen) - for n, mapValue := range mapValues { - v, _ := strconv.ParseInt(mapValue, 10, 64) - fieldData[n] = v - } - fieldDataValue := reflect.ValueOf(fieldData) - field.Set(fieldDataValue) + // The value of the field as string: + mapValue := data[mapName][0] + + // The value of the field as bytes: + bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue) + if errBase64 != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error()) + } else { + // The destination: + var destination []int64 + + // A reader for the bytes: + buffer := bytes.NewReader(bytesArray) + + // Try to decode the bytes to an instance of the type: + errBinary := binary.Read(buffer, binary.LittleEndian, &destination) + if errBinary != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the binary data to the type of the ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBinary.Error()) + } else { + // Finnaly, store the value in the message: + fieldDataValue := reflect.ValueOf(destination) + field.Set(fieldDataValue) + } + } case `[]bool`: + // The name of the field: mapName := fmt.Sprintf(`bool[]:%s`, elementType.Field(i).Name) - mapValues := data[mapName] - fieldLen := len(mapValues) - fieldData := make([]bool, fieldLen, fieldLen) - for n, mapValue := range mapValues { - v, _ := strconv.ParseBool(mapValue) - fieldData[n] = v - } - fieldDataValue := reflect.ValueOf(fieldData) - field.Set(fieldDataValue) + // The value of the field as string: + mapValue := data[mapName][0] + + // The value of the field as bytes: + bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue) + if errBase64 != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error()) + } else { + fieldLen := len(bytesArray) + fieldData := make([]bool, fieldLen, fieldLen) + + // Convert each byte in a bool: + for n, value := range bytesArray { + if value == 0x1 { + fieldData[n] = true + } else { + fieldData[n] = false + } + } + + // Store the values in the message: + fieldDataValue := reflect.ValueOf(fieldData) + field.Set(fieldDataValue) + } case `[]string`: + // The name of the field: mapName := fmt.Sprintf(`str[]:%s`, elementType.Field(i).Name) - mapValues := data[mapName] - fieldDataValue := reflect.ValueOf(mapValues) - field.Set(fieldDataValue) - case `[]float64`: - mapName := fmt.Sprintf(`f64[]:%s`, elementType.Field(i).Name) - mapValues := data[mapName] - fieldLen := len(mapValues) - fieldData := make([]float64, fieldLen, fieldLen) - for n, mapValue := range mapValues { - v, _ := strconv.ParseFloat(mapValue, 64) - fieldData[n] = v + // The value of the field as string: + mapValue := data[mapName][0] + + // The value of the field as bytes: + bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue) + if errBase64 != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error()) + } else { + // Get the URL encoded string of all values: + allStringsRAW := string(bytesArray) + + // Split now the different strings: + allStrings := strings.Split(allStringsRAW, "\n") + + // A place where we store the final strings: + data := make([]string, len(allStrings), len(allStrings)) + + // Loop over all URL encoded strings and decode it: + for n, element := range allStrings { + elementFinal, errURL := url.QueryUnescape(element) + if errURL != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode a base64 string for a string array.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errURL.Error()) + } else { + data[n] = elementFinal + } + } + + // Store the values in the message: + fieldDataValue := reflect.ValueOf(data) + field.Set(fieldDataValue) } - fieldDataValue := reflect.ValueOf(fieldData) - field.Set(fieldDataValue) + case `[]float64`: + // The name of the field: + mapName := fmt.Sprintf(`f64[]:%s`, elementType.Field(i).Name) + + // The value of the field as string: + mapValue := data[mapName][0] + + // The value of the field as bytes: + bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue) + if errBase64 != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error()) + } else { + // The destination: + var destination []float64 + + // A reader for the bytes: + buffer := bytes.NewReader(bytesArray) + + // Try to decode the bytes to an instance of the type: + errBinary := binary.Read(buffer, binary.LittleEndian, &destination) + if errBinary != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the binary data to the type of the ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBinary.Error()) + } else { + // Finnaly, store the value in the message: + fieldDataValue := reflect.ValueOf(destination) + field.Set(fieldDataValue) + } + } } } } diff --git a/ICCC/Doc.go b/ICCC/Doc.go index 76578e9..c2f81aa 100644 --- a/ICCC/Doc.go +++ b/ICCC/Doc.go @@ -7,9 +7,9 @@ To be able to marshal / parse the data back to objects, some additional informat Example 01: name=str:Surname -value=Sommer +value=U29tbWVy -The HTTP form name is 'str:Surname' and the value is 'Sommer'. The 'str' is the indicator for the data type, in this case it is a string. +The HTTP form name is 'str:Surname' and the value is 'Sommer' as base64 encoded string. The 'str' is the indicator for the data type, in this case it is a string. Known data types are: * str := string @@ -23,18 +23,17 @@ Known data types are: * str[] := string array * f64[] := 64 bit float array -Formatting of the corresponding values (each value is a string => HTTP). Plase note: -For the arrays, the name will repeated for each value. -* str := the plain UTF8 string -* int := the integer e.g. '13894612' -* f64 := the float with nine digits e.g. 9.48 gets '9.480000000' -* bool := 'true' or 'false' (lower case) -* ui8 := the byte as hexadecimal string e.g. 255 gets 'ff' -* ui8[] := the bytes as hexdecimal strings e.g. 0 255 0 gets ui8[]:name:00 ui8[]:name:ff ui8[]:name:00 -* int[] := 64 bit integer array e.g. 1 2 gets int[]:name:1 int[]:name:2 -* bool[] := a boolean array e.g. true true gets bool[]:name:true bool[]:name:true -* str[] := string array e.g. 'a' 'abc' gets str[]:name:a str[]:name:abc -* f64[] := 64 bit float array e.g. 1.1 1.2 gets f64[]:name:1.100000000 f64[]:name:1.2000000000 +Formatting of the corresponding values (each value is at the end a base64 string). +* str := the plain UTF8 string as URL encoded. These bytes are getting base64 encoded. +* int := the little endian representation of the int. These bytes are getting base64 encoded. +* f64 := the little endian representation of the float. These bytes are getting base64 encoded. +* bool := the byte 0x1 or 0x0 for true and false. These byte will be base64 encoded. +* ui8 := These byte will be base64 encoded. +* ui8[] := These bytes are getting base64 encoded. +* int[] := the little endian representation of the integers. These bytes are getting base64 encoded. +* bool[] := the bools are getting converted to bytes (0x1 or 0x0 for true and false). These bytes are getting base64 encoded. +* str[] := each string will be URL encoded. Afterwards, join all strings by \n. These bytes are getting base64 encoded. +* f64[] := the little endian representation of the floats. These bytes are getting base64 encoded. The format of a message is: command=COMMAND diff --git a/ICCC/Message2Data.go b/ICCC/Message2Data.go index bb20921..65fe834 100644 --- a/ICCC/Message2Data.go +++ b/ICCC/Message2Data.go @@ -1,11 +1,16 @@ package ICCC import ( + "bytes" + "encoding/base64" + "encoding/binary" "fmt" "github.com/SommerEngineering/Ocean/Log" LM "github.com/SommerEngineering/Ocean/Log/Meta" + "io" + "net/url" "reflect" - "strconv" + "strings" ) // Function to convert an ICCC message to HTTP data. @@ -39,75 +44,134 @@ func Message2Data(channel, command string, message interface{}) (data map[string field := element.Field(i) keyName := elementType.Field(i).Name + // A buffer for the binary representation: + buffer := new(bytes.Buffer) + + // For possible errors: + var errConverter error = nil + + // The key for this element: + key := `` + + // Look for the right data type: switch field.Kind().String() { case `int64`: - key := fmt.Sprintf(`int:%s`, keyName) - data[key] = []string{strconv.FormatInt(field.Int(), 10)} + key = fmt.Sprintf(`int:%s`, keyName) + + // Converts the value in a byte array: + errConverter = binary.Write(buffer, binary.LittleEndian, field.Int()) case `string`: - key := fmt.Sprintf(`str:%s`, keyName) - data[key] = []string{field.String()} + key = fmt.Sprintf(`str:%s`, keyName) + + // URL encode the string and copy its bytes to the buffer: + io.Copy(buffer, strings.NewReader(url.QueryEscape(field.String()))) case `float64`: - key := fmt.Sprintf(`f64:%s`, keyName) - data[key] = []string{strconv.FormatFloat(field.Float(), 'f', 9, 64)} + key = fmt.Sprintf(`f64:%s`, keyName) + + // Converts the value in a byte array: + errConverter = binary.Write(buffer, binary.LittleEndian, field.Float()) case `bool`: - key := fmt.Sprintf(`bool:%s`, keyName) - data[key] = []string{strconv.FormatBool(field.Bool())} + key = fmt.Sprintf(`bool:%s`, keyName) - case `uint8`: // byte - key := fmt.Sprintf(`ui8:%s`, keyName) - data[key] = []string{strconv.FormatUint(field.Uint(), 16)} + // Directly convert the bool in a byte: + if field.Bool() { + // Case: True + buffer.WriteByte(0x1) // Write 1 + } else { + // Case: False + buffer.WriteByte(0x0) // Write 0 + } + + case `uint8`: // a byte + key = fmt.Sprintf(`ui8:%s`, keyName) + + // uint8 is a byte, thus, write it directly in the buffer: + buffer.WriteByte(byte(field.Uint())) // Case: Arrays... case `slice`: sliceLen := field.Len() if sliceLen > 0 { + + // Which kind of data is this? sliceKind := field.Index(0).Kind() - key := `` - dataValues := make([]string, sliceLen, sliceLen) + + // Select the right data type: switch sliceKind.String() { - case `uint8`: // bytes + case `uint8`: // a byte array key = fmt.Sprintf(`ui8[]:%s`, keyName) values := field.Interface().([]uint8) - for index, value := range values { - dataValues[index] = strconv.FormatUint(uint64(value), 16) + + // Directly write the bytes in the buffer: + for _, val := range values { + buffer.WriteByte(byte(val)) } case `int64`: key = fmt.Sprintf(`int[]:%s`, keyName) values := field.Interface().([]int64) - for index, value := range values { - dataValues[index] = strconv.FormatInt(value, 10) - } + + // Converts the array in a byte array: + errConverter = binary.Write(buffer, binary.LittleEndian, values) case `bool`: key = fmt.Sprintf(`bool[]:%s`, keyName) values := field.Interface().([]bool) - for index, value := range values { - dataValues[index] = strconv.FormatBool(value) + + // Cannot convert bool to bytes by using binary.Write(). Thus, + // convert it by ower own: + + // Loop over all values: + for _, val := range values { + if val { + // If the value is true: + buffer.WriteByte(0x1) // Write 1 + } else { + // If the value is false: + buffer.WriteByte(0x0) // Write 0 + } } case `string`: key = fmt.Sprintf(`str[]:%s`, keyName) values := field.Interface().([]string) - for index, value := range values { - dataValues[index] = value + + // Mask every string by using a URL encoding. + // This masks e.g. every new-line i.e \n, etc. + // This allows us to combine later the strings by + // using \n: + masked := make([]string, len(values)) + + // Loop over each string and convert it: + for n, val := range values { + masked[n] = url.QueryEscape(val) } + // Join all masked strings by using \n and copy the byte array + // representation in the buffer: + io.Copy(buffer, strings.NewReader(strings.Join(masked, "\n"))) + case `float64`: key = fmt.Sprintf(`f64[]:%s`, keyName) values := field.Interface().([]float64) - for index, value := range values { - dataValues[index] = strconv.FormatFloat(value, 'f', 9, 64) - } - } - data[key] = dataValues + // Converts the array in a byte array: + errConverter = binary.Write(buffer, binary.LittleEndian, values) + } } } + + if errConverter != nil { + // An error occurs: + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `It was not possible to convert an array for an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errConverter.Error()) + } else { + // Convert the byte array to a base64 string for the transportation on wire: + data[key] = []string{base64.StdEncoding.EncodeToString(buffer.Bytes())} + } } return