diff --git a/lib/common/mapping.go b/lib/common/mapping.go index d2f1abd..14fc973 100644 --- a/lib/common/mapping.go +++ b/lib/common/mapping.go @@ -14,24 +14,31 @@ type MappingValues [][2]String // the form of a MappingValues. // func (mapping Mapping) Values() (map_values MappingValues, errs []error) { - // check length and append any errors needed, max: 65537 - // sanity check no data is missing - var str String var remainder = mapping var err error + + length := Integer(remainder[:2]) + remainder = remainder[2:] + mapping_len := len(mapping) + if mapping_len > length+2 { + errs = append(errs, errors.New("warning parsing mapping: data exists beyond length of mapping")) + } else if length+2 > mapping_len { + errs = append(errs, errors.New("warning parsing mapping: mapping length exceeds provided data")) + } + for { // Read a key, breaking on fatal errors // and appending warnings str, remainder, err = ReadString(remainder) key_str := str if err != nil { - errs = append(errs, err) if stopValueRead(err) { + errs = append(errs, err) return } } - if !beginsWith(remainder, "=") { + if !beginsWith(remainder, 0x3d) { errs = append(errs, errors.New("mapping format violation, expected =")) return } @@ -42,12 +49,12 @@ func (mapping Mapping) Values() (map_values MappingValues, errs []error) { str, remainder, err = ReadString(remainder) val_str := str if err != nil { - errs = append(errs, err) if stopValueRead(err) { + errs = append(errs, err) return } } - if !beginsWith(remainder, ";") { + if !beginsWith(remainder, 0x3b) { errs = append(errs, errors.New("mapping format violation, expected ;")) return } @@ -67,6 +74,16 @@ func (mapping Mapping) Values() (map_values MappingValues, errs []error) { // Returns true if two keys in a mapping are identical // func (mapping Mapping) HasDuplicateKeys() bool { + seen_values := make(map[string]bool) + values, _ := mapping.Values() + for _, pair := range values { + key, _ := pair[0].Data() + if _, present := seen_values[key]; present { + return true + } else { + seen_values[key] = true + } + } return false } @@ -79,8 +96,10 @@ func ValuesToMapping(values MappingValues) Mapping { var mapping Mapping mappingOrder(values) for _, kv_pair := range values { - key_string := String(kv_pair[0]) - key_value := String(kv_pair[1]) + key_string := kv_pair[0] + key_string = append(key_string, []byte("=")[0]) + key_value := kv_pair[1] + key_value = append(key_value, []byte(";")[0]) mapping = append(append(mapping, key_string...), key_value...) } map_len := len(mapping) @@ -152,7 +171,7 @@ func stopValueRead(err error) bool { return err.Error() == "error parsing string: zero length" } -func beginsWith(bytes []byte, str string) bool { +func beginsWith(bytes []byte, chr byte) bool { return len(bytes) != 0 && - string(bytes[0]) == str + bytes[0] == chr } diff --git a/lib/common/mapping_test.go b/lib/common/mapping_test.go index ecae0c9..7a46d45 100644 --- a/lib/common/mapping_test.go +++ b/lib/common/mapping_test.go @@ -1,5 +1,168 @@ package common import ( -//"testing" + "bytes" + "errors" + "testing" ) + +func TestValuesExclusesPairWithBadData(t *testing.T) { + bad_key := Mapping([]byte{0x00, 0x0c, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b, 0x00}) + values, errs := bad_key.Values() + if len(values) != 1 { + t.Fatal("Values did not return valid values when some values had bad key") + } else { + key, _ := values[0][0].Data() + val, _ := values[0][1].Data() + if key != "a" || val != "b" { + t.Fatal("Value returned by values when other value had invalid key was incorrect") + } + } + if len(errs) != 2 { + t.Fatal("Values reported wrong error count when some values had invalid data", errs) + } +} + +func TestValuesWarnsMissingData(t *testing.T) { + mapping := Mapping([]byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62}) + _, errs := mapping.Values() + if len(errs) != 2 || errs[0].Error() != "warning parsing mapping: mapping length exceeds provided data" { + t.Fatal("Values reported wrong error when missing data", len(errs), errs) + } +} + +func TestValuesWarnsExtraData(t *testing.T) { + mapping := Mapping([]byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b, 0x00}) + _, errs := mapping.Values() + if len(errs) != 2 || errs[0].Error() != "warning parsing mapping: data exists beyond length of mapping" { + t.Fatal("Values reported wrong error when extra data", len(errs), errs) + } +} + +func TestValuesEnforcesEqualDelimitor(t *testing.T) { + mapping := Mapping([]byte{0x00, 0x06, 0x01, 0x61, 0x30, 0x01, 0x62, 0x3b}) + values, errs := mapping.Values() + if len(errs) != 1 || errs[0].Error() != "mapping format violation, expected =" { + t.Fatal("wrong error reported with equal format error", errs) + } + if len(values) != 0 { + t.Fatal("values not empty with invalid data, equal error") + } +} + +func TestValuesEnforcedSemicolonDelimitor(t *testing.T) { + mapping := Mapping([]byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x30}) + values, errs := mapping.Values() + if len(errs) != 1 || errs[0].Error() != "mapping format violation, expected ;" { + t.Fatal("wrong error reported with semicolon format error", errs) + } + if len(values) != 0 { + t.Fatal("values not empty with invalid data, semicolon error") + } +} + +func TestValuesReturnsValues(t *testing.T) { + mapping := Mapping([]byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b}) + _, errs := mapping.Values() + if errs != nil { + t.Fatal("errs when parsing valid mapping values", errs) + } +} + +func TestHasDuplicateKeysTrueWhenDuplicates(t *testing.T) { + dups := Mapping([]byte{0x00, 0x0c, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b}) + if dups.HasDuplicateKeys() != true { + t.Fatal("HasDuplicateKeys did not report true when duplicate keys present") + } +} + +func TestHasDuplicateKeysFalseWithoutDuplicates(t *testing.T) { + mapping := Mapping([]byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b}) + if mapping.HasDuplicateKeys() != false { + t.Fatal("HasDuplicateKeys did not report false when duplicate keys were not present") + } +} + +func TestGoMapToMappingProducesCorrectMapping(t *testing.T) { + gomap := map[string]string{"a": "b"} + mapping, err := GoMapToMapping(gomap) + if err != nil { + t.Fatal("GoMapToMapping returned error with valid data", err) + } + expected := []byte{0x00, 0x06, 0x01, 0x61, 0x3d, 0x01, 0x62, 0x3b} + if bytes.Compare(mapping, expected) != 0 { + t.Fatal("GoMapToMapping did not produce correct Mapping", mapping, expected) + } +} + +func TestMappingOrderSortsValuesThenKeys(t *testing.T) { + a, _ := ToI2PString("a") + b, _ := ToI2PString("b") + values := MappingValues{ + [2]String{b, b}, + [2]String{b, a}, + [2]String{a, b}, + [2]String{a, a}, + } + mappingOrder(values) + for i, pair := range values { + key, _ := pair[0].Data() + value, _ := pair[1].Data() + switch i { + case 0: + if !(key == "a" && value == "a") { + t.Fatal("mappingOrder produced incorrect sort output at", i) + } + case 1: + if !(key == "a" && value == "b") { + t.Fatal("mappingOrder produced incorrect sort output at", i) + } + case 2: + if !(key == "b" && value == "a") { + t.Fatal("mappingOrder produced incorrect sort output at", i) + } + case 3: + if !(key == "b" && value == "b") { + t.Fatal("mappingOrder produced incorrect sort output at", i) + } + } + } +} + +func TestStopValueReadTrueWhenCorrectErr(t *testing.T) { + status := stopValueRead(errors.New("error parsing string: zero length")) + if status != true { + t.Fatal("stopValueRead not true when String error found") + } +} + +func TestStopValueReadFalseWhenWrongErr(t *testing.T) { + status := stopValueRead(errors.New("something else")) + if status != false { + t.Fatal("stopValueRead not false when error not String error") + } +} + +func TestBeginsWithCorrectWhenTrue(t *testing.T) { + slice := []byte{0x41} + status := beginsWith(slice, 0x41) + if status != true { + t.Fatal("beginsWith did not return false on empty slice") + } +} + +func TestBeginsWithCorrectWhenFalse(t *testing.T) { + slice := []byte{0x00} + status := beginsWith(slice, 0x41) + if status != false { + t.Fatal("beginsWith did not return false on empty slice") + } +} + +func TestBeginsWithCorrectWhenNil(t *testing.T) { + slice := make([]byte, 0) + status := beginsWith(slice, 0x41) + if status != false { + t.Fatal("beginsWith did not return false on empty slice") + } +} diff --git a/lib/common/string_test.go b/lib/common/string_test.go index acf0065..3a77e49 100644 --- a/lib/common/string_test.go +++ b/lib/common/string_test.go @@ -106,43 +106,43 @@ func TestReadStringReadsLength(t *testing.T) { bytes := []byte{0x01, 0x04, 0x06} str, remainder, err := ReadString(bytes) if err == nil || err.Error() != "string parsing warning: string contains data beyond length" { - t.Fatal("ReadString(t *testing.T) returned incorrect error,", err) + t.Fatal("ReadString() returned incorrect error,", err) } if len(str) != 2 { - t.Fatal("ReadString(t *testing.T) did not return correct string length:", len(str)) + t.Fatal("ReadString() did not return correct string length:", len(str)) } if str[0] != 0x01 && str[1] != 0x04 { - t.Fatal("ReadString(t *testing.T) did not return correct string") + t.Fatal("ReadString() did not return correct string") } if len(remainder) != 1 { - t.Fatal("ReadString(t *testing.T) did not return correct remainder length") + t.Fatal("ReadString() did not return correct remainder length") } if remainder[0] != 0x06 { - t.Fatal("ReadString(t *testing.T) did not return correct remainder") + t.Fatal("ReadString() did not return correct remainder") } } func TestReadStringErrWhenEmptySlice(t *testing.T) { bytes := make([]byte, 0) _, _, err := ReadString(bytes) - if err != nil && err.Error() != "error parsing string: zero length" { - t.Fatal("ReadString(t *testing.T) did not report empty slice error", err) + if err == nil || err.Error() != "error parsing string: zero length" { + t.Fatal("ReadString() did not report empty slice error", err) } } func TestReadStringErrWhenDataTooShort(t *testing.T) { - bytes := []byte{0x03, 0x01} - str, remainder, err := ReadString(bytes) - if err != nil && err.Error() != "string parsing warning: string data is shorter than specified by length" { - t.Fatal("ReadString(t *testing.T) did not report string too long", err) + short_str := []byte{0x03, 0x01} + str, remainder, err := ReadString(short_str) + if err == nil || err.Error() != "string parsing warning: string data is shorter than specified by length" { + t.Fatal("ReadString() did not report string too long:", err) } if len(str) != 2 { - t.Fatal("ReadString(t *testing.T) did not return the slice as string when too long") + t.Fatal("ReadString() did not return the slice as string when too long") } if str[0] != 0x03 && str[1] != 0x01 { - t.Fatal("ReadString(t *testing.T) did not return the correct partial string") + t.Fatal("ReadString() did not return the correct partial string") } if len(remainder) != 0 { - t.Fatal("ReadString(t *testing.T) returned a remainder when the string data was too short") + t.Fatal("ReadString() returned a remainder when the string data was too short") } }