seekia/internal/byteRange/byteRange.go

701 lines
22 KiB
Go

// byteRange provides functions to read and generate byte ranges
// Byte ranges are used to describe what range of identity hashes/inboxes to retrieve from a host
// Hosts share the range of identities/inboxes they host on their profile.
package byteRange
import "seekia/internal/identity"
import "seekia/internal/encoding"
import "math"
import "math/big"
import "errors"
// This is used to retrieve the minimum and maximum identity hash ranges
// This is used when a server request does not need to filter based on a range
func GetMinimumMaximumIdentityHashBounds()([16]byte, [16]byte){
// minimumBound == "aaaaaaaaaaaaaaaaaaaaaaaam"
minimumBound := [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
// maximumBound == "777777777777777777777777r"
maximumBound := [16]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 3}
return minimumBound, maximumBound
}
// This is used to retrieve the minimum and maximum inbox ranges
// This is used when a server request does not need to filter based on a range
func GetMinimumMaximumInboxBounds()([10]byte, [10]byte){
// minimumBound == "aaaaaaaaaaaaaaaa"
minimumBound := [10]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
// maximumBound == "7777777777777777"
maximumBound := [10]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
return minimumBound, maximumBound
}
func CheckIfIdentityHashIsWithinRange(rangeStart [16]byte, rangeEnd [16]byte, inputIdentityHash [16]byte)(bool, error){
isValid, err := identity.VerifyIdentityHash(inputIdentityHash, false, "")
if (err != nil) { return false, err }
if (isValid == false){
inputIdentityHashHex := encoding.EncodeBytesToHexString(inputIdentityHash[:])
return false, errors.New("CheckIfIdentityHashIsWithinRange called with invalid inputIdentityHash: " + inputIdentityHashHex)
}
if (rangeStart == inputIdentityHash || rangeEnd == inputIdentityHash){
return true, nil
}
if (rangeStart == rangeEnd){
return false, nil
}
isWithinRange, err := checkIfBytesAreWithinRange(rangeStart[:], rangeEnd[:], inputIdentityHash[:])
if (err != nil) { return false, err }
return isWithinRange, nil
}
func CheckIfInboxIsWithinRange(rangeStart [10]byte, rangeEnd [10]byte, inputInbox [10]byte)(bool, error){
if (rangeStart == inputInbox || rangeEnd == inputInbox){
return true, nil
}
if (rangeStart == rangeEnd){
return false, nil
}
isWithinRange, err := checkIfBytesAreWithinRange(rangeStart[:], rangeEnd[:], inputInbox[:])
if (err != nil) { return false, err }
return isWithinRange, nil
}
func checkIfBytesAreWithinRange(rangeStart []byte, rangeEnd []byte, inputSlice []byte)(bool, error){
// We check to see if inputSlice is between both values
slicesAreEqual, comparisonA, err := compareByteSlices(rangeStart, inputSlice)
if (err != nil) { return false, err }
if (slicesAreEqual == true){
return true, nil
}
slicesAreEqual, comparisonB, err := compareByteSlices(rangeEnd, inputSlice)
if (err != nil) { return false, err }
if (slicesAreEqual == true){
return true, nil
}
if (comparisonA != comparisonB){
return true, nil
}
return false, nil
}
//Outputs:
// -bool: Any values found
// -[][16]byte: List of values that are within range
// -error
func GetAllIdentityHashesInListWithinRange(rangeStart [16]byte, rangeEnd [16]byte, inputList [][16]byte)(bool, [][16]byte, error){
identitiesWithinRangeList := make([][16]byte, 0)
for _, identityHash := range inputList{
isValid, err := identity.VerifyIdentityHash(identityHash, false, "")
if (err != nil) { return false, nil, err }
if (isValid == false){
identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
return false, nil, errors.New("GetAllIdentityHashesInListWithinRange called with inputList containing invalid identityHash: " + identityHashHex)
}
isWithinRange, err := checkIfBytesAreWithinRange(rangeStart[:], rangeEnd[:], identityHash[:])
if (err != nil) { return false, nil, err }
if (isWithinRange == true){
identitiesWithinRangeList = append(identitiesWithinRangeList, identityHash)
}
}
if (len(identitiesWithinRangeList) == 0){
return false, nil, nil
}
return true, identitiesWithinRangeList, nil
}
//Outputs:
// -bool: Any inboxes found
// -[][10]byte: List of inboxes that are within range
// -error
func GetAllInboxesInListWithinRange(rangeStart [10]byte, rangeEnd [10]byte, inputList [][10]byte)(bool, [][10]byte, error){
inboxesWithinRangeList := make([][10]byte, 0)
for _, inbox := range inputList{
isWithinRange, err := checkIfBytesAreWithinRange(rangeStart[:], rangeEnd[:], inbox[:])
if (err != nil) { return false, nil, err }
if (isWithinRange == true){
inboxesWithinRangeList = append(inboxesWithinRangeList, inbox)
}
}
if (len(inboxesWithinRangeList) == 0){
return false, nil, nil
}
return true, inboxesWithinRangeList, nil
}
// This function will find the shared range from two ranges
//Outputs:
// -bool: Any intersection found
// -[16]byte: Intersection range start
// -[16]byte: Intersection range end
// -error
func GetIdentityIntersectionRangeFromTwoRanges(range1Start [16]byte, range1End [16]byte, range2Start [16]byte, range2End [16]byte)(bool, [16]byte, [16]byte, error){
anyIntersectionFound, intersectionRangeStart, intersectionRangeEnd, err := getIntersectionRangeFromTwoRanges(range1Start[:], range1End[:], range2Start[:], range2End[:])
if (err != nil) { return false, [16]byte{}, [16]byte{}, err }
if (anyIntersectionFound == false){
return false, [16]byte{}, [16]byte{}, nil
}
if (len(intersectionRangeStart) != 16){
return false, [16]byte{}, [16]byte{}, errors.New("getIntersectionRangeFromTwoRanges returning invalid length range start.")
}
if (len(intersectionRangeEnd) != 16){
return false, [16]byte{}, [16]byte{}, errors.New("getIntersectionRangeFromTwoRanges returning invalid length range end.")
}
intersectionRangeStartArray := [16]byte(intersectionRangeStart)
intersectionRangeEndArray := [16]byte(intersectionRangeEnd)
return true, intersectionRangeStartArray, intersectionRangeEndArray, nil
}
// This function will find the shared range from two ranges
//Outputs:
// -bool: Any intersection found
// -[10]byte: Intersection range start
// -[10]byte: Intersection range end
// -error
func GetInboxIntersectionRangeFromTwoRanges(range1Start [10]byte, range1End [10]byte, range2Start [10]byte, range2End [10]byte)(bool, [10]byte, [10]byte, error){
anyIntersectionFound, intersectionRangeStart, intersectionRangeEnd, err := getIntersectionRangeFromTwoRanges(range1Start[:], range1End[:], range2Start[:], range2End[:])
if (err != nil) { return false, [10]byte{}, [10]byte{}, err }
if (anyIntersectionFound == false){
return false, [10]byte{}, [10]byte{}, nil
}
if (len(intersectionRangeStart) != 10){
return false, [10]byte{}, [10]byte{}, errors.New("getIntersectionRangeFromTwoRanges returning invalid length rangeStart.")
}
if (len(intersectionRangeEnd) != 10){
return false, [10]byte{}, [10]byte{}, errors.New("getIntersectionRangeFromTwoRanges returning invalid length rangeEnd.")
}
intersectionRangeStartArray := [10]byte(intersectionRangeStart)
intersectionRangeEndArray := [10]byte(intersectionRangeEnd)
return true, intersectionRangeStartArray, intersectionRangeEndArray, nil
}
// This function will find the shared range from two ranges
//Outputs:
// -bool: Any intersection found
// -[]byte: Intersection range start
// -[]byte: Intersection range end
// -error
func getIntersectionRangeFromTwoRanges(range1Start []byte, range1End []byte, range2Start []byte, range2End []byte)(bool, []byte, []byte, error){
if (string(range1Start) == string(range1End) && string(range2Start) == string(range2End)){
// The intersection is either 1 value, or none
if (string(range1Start) == string(range2Start)){
return true, range1Start, range1Start, nil
}
return false, nil, nil, nil
}
if (string(range1Start) == string(range1End)){
// We know range2Start != range2End
isInRange, err := checkIfBytesAreWithinRange(range2Start, range2End, range1Start)
if (err != nil) { return false, nil, nil, err }
if (isInRange == true){
// Intersection is a single value
return true, range1Start, range1Start, nil
}
return false, nil, nil, nil
}
if (string(range2Start) == string(range2End)){
// We know range1Start != range1End
isInRange, err := checkIfBytesAreWithinRange(range1Start, range1End, range2Start)
if (err != nil) { return false, nil, nil, err }
if (isInRange == true){
// Intersection is a single value
return true, range2Start, range2Start, nil
}
return false, nil, nil, nil
}
//Outputs:
// -[]byte: Smaller range bound
// -[]byte: Larger range bound
// -error
getSmallerAndLargerRangeBounds := func(bound1 []byte, bound2 []byte)([]byte, []byte, error){
boundsAreEqual, latterIsLarger, err := compareByteSlices(bound1, bound2)
if (err != nil){ return nil, nil, err }
if (boundsAreEqual == true){
return nil, nil, errors.New("compareByteSlices returning bounds are equal after we already checked.")
}
if (latterIsLarger == true){
return bound1, bound2, nil
}
return bound2, bound1, nil
}
smallerBound1, largerBound1, err := getSmallerAndLargerRangeBounds(range1Start, range1End)
if (err != nil) { return false, nil, nil, err }
smallerBound2, largerBound2, err := getSmallerAndLargerRangeBounds(range2Start, range2End)
if (err != nil) { return false, nil, nil, err }
boundsAreEqual, latterIsLarger, err := compareByteSlices(largerBound1, smallerBound2)
if (err != nil) { return false, nil, nil, err }
if (boundsAreEqual == true){
// The intersection is only this 1 value
return true, largerBound1, smallerBound2, nil
}
if (latterIsLarger == true){
// There is no intersection
return false, nil, nil, nil
}
// There is some overlap between the ranges
getIntersectionSmallerBound := func()([]byte, error){
// We return the greater of the two bounds
boundsAreEqual, latterIsLarger, err := compareByteSlices(smallerBound1, smallerBound2)
if (err != nil) { return nil, err }
if (boundsAreEqual == true){
return smallerBound2, nil
}
if (latterIsLarger == true){
return smallerBound2, nil
}
return smallerBound1, nil
}
intersectionSmallerBound, err := getIntersectionSmallerBound()
if (err != nil) { return false, nil, nil, err }
getIntersectionLargerBound := func()([]byte, error){
// We return the smaller of the two bounds
boundsAreEqual, latterIsLarger, err := compareByteSlices(largerBound1, largerBound2)
if (err != nil) { return nil, err }
if (boundsAreEqual == true){
return largerBound1, nil
}
if (latterIsLarger == true){
return largerBound1, nil
}
return largerBound2, nil
}
intersectionLargerBound, err := getIntersectionLargerBound()
if (err != nil) { return false, nil, nil, err }
return true, intersectionSmallerBound, intersectionLargerBound, nil
}
func GetEstimatedIdentitySubrangeQuantity(fullRangeStart [16]byte, fullRangeEnd [16]byte, fullRangeQuantity int64, subrangeStart [16]byte, subrangeEnd [16]byte)(int64, error){
estimatedIdentitiesWithinRange, err := getEstimatedSubrangeQuantity(fullRangeStart[:], fullRangeEnd[:], fullRangeQuantity, subrangeStart[:], subrangeEnd[:])
if (err != nil) { return 0, err }
return estimatedIdentitiesWithinRange, nil
}
func GetEstimatedInboxSubrangeQuantity(fullRangeStart [10]byte, fullRangeEnd [10]byte, fullRangeQuantity int64, subrangeStart [10]byte, subrangeEnd [10]byte)(int64, error){
estimatedInboxesWithinRange, err := getEstimatedSubrangeQuantity(fullRangeStart[:], fullRangeEnd[:], fullRangeQuantity, subrangeStart[:], subrangeEnd[:])
if (err != nil) { return 0, err }
return estimatedInboxesWithinRange, nil
}
// This function takes a range and its item quantity, and a subset range, and returns the estimated number of items within the subset range
// This is used to determine how many items will be in a request, so that the requestor can know the maximum range to request
// This is because requests and responses have a maximum size, so the requestor must only request enough items to not go over the limit
// Outputs:
// -int64: Estimated number of items in subrange
// -error
func getEstimatedSubrangeQuantity(fullRangeStart []byte, fullRangeEnd []byte, fullRangeQuantityInt64 int64, subRangeStart []byte, subRangeEnd []byte)(int64, error){
// First we make sure the subrange is a valid subrange of the full range.
isInRange, err := checkIfBytesAreWithinRange(fullRangeStart, fullRangeEnd, subRangeStart)
if (err != nil) { return 0, err }
if (isInRange == false){
return 0, errors.New("getEstimatedSubrangeQuantity called with a start bound out of range.")
}
isInRange, err = checkIfBytesAreWithinRange(fullRangeStart, fullRangeEnd, subRangeEnd)
if (err != nil) { return 0, err }
if (isInRange == false){
return 0, errors.New("getEstimatedSubrangeQuantity called with a end bound out of range.")
}
if (fullRangeQuantityInt64 == 0){
return 0, nil
}
_, _, _, fullRangeLength, err := getRangeLength(fullRangeStart, fullRangeEnd)
if (err != nil) { return 0, err }
fullRangeQuantity := big.NewInt(fullRangeQuantityInt64)
_, _, _, subRangeLength, err := getRangeLength(subRangeStart, subRangeEnd)
if (err != nil){ return 0, err }
// FullRangeLength/FullRangeQuantity = SubrangeLength/SubrangeQuantity
// We solve for SubrangeQuantity
// SubrangeQuantity/SubrangeLength = FullRangeQuantity/FullRangeLength
// SubrangeQuantity = (FullRangeQuantity * SubrangeLength)/FullRangeLength
numerator := new(big.Int)
numerator.Mul(fullRangeQuantity, subRangeLength)
subrangeQuantity := new(big.Int)
subrangeQuantity.Div(numerator, fullRangeLength)
isInt64 := subrangeQuantity.IsInt64()
if (isInt64 == false){
// Number is too large to represent as int64
// This should never happen in practice, so host must be malicious
// We will return maximum int64 value
return 9223372036854775807, nil
}
result := subrangeQuantity.Int64()
return result, nil
}
type IdentitySubrange struct{
SubrangeStart [16]byte
SubrangeEnd [16]byte
}
type InboxSubrange struct{
SubrangeStart [10]byte
SubrangeEnd [10]byte
}
func SplitIdentityRangeIntoEqualSubranges(fullRangeStart [16]byte, fullRangeEnd [16]byte, fullRangeQuantity int64, maximumIdentitiesPerSubrange int64)([]IdentitySubrange, error){
subrangeObjectsList, err := splitRangeIntoEqualSubranges(16, fullRangeStart[:], fullRangeEnd[:], fullRangeQuantity, maximumIdentitiesPerSubrange)
if (err != nil) { return nil, err }
identitySubrangesList := make([]IdentitySubrange, 0, len(subrangeObjectsList))
for _, subrangeObject := range subrangeObjectsList{
subrangeStart := subrangeObject.SubrangeStart
subrangeEnd := subrangeObject.SubrangeEnd
if (len(subrangeStart) != 16){
return nil, errors.New("splitRangeIntoEqualSubranges returning invalid subrangeObject with invalid length subrangeStart.")
}
if (len(subrangeEnd) != 16){
return nil, errors.New("splitRangeIntoEqualSubranges returning invalid subrangeObject with invalid length subrangeEnd.")
}
subrangeStartArray := [16]byte(subrangeStart)
subrangeEndArray := [16]byte(subrangeEnd)
identitySubrangeObject := IdentitySubrange{
SubrangeStart: subrangeStartArray,
SubrangeEnd: subrangeEndArray,
}
identitySubrangesList = append(identitySubrangesList, identitySubrangeObject)
}
return identitySubrangesList, nil
}
func SplitInboxRangeIntoEqualSubranges(fullRangeStart [10]byte, fullRangeEnd [10]byte, fullRangeQuantity int64, maximumInboxesPerSubrange int64)([]InboxSubrange, error){
subrangeObjectsList, err := splitRangeIntoEqualSubranges(10, fullRangeStart[:], fullRangeEnd[:], fullRangeQuantity, maximumInboxesPerSubrange)
if (err != nil) { return nil, err }
inboxSubrangesList := make([]InboxSubrange, 0, len(subrangeObjectsList))
for _, subrangeObject := range subrangeObjectsList{
subrangeStart := subrangeObject.SubrangeStart
subrangeEnd := subrangeObject.SubrangeEnd
if (len(subrangeStart) != 10){
return nil, errors.New("splitRangeIntoEqualSubranges returning invalid subrangeObject with invalid length subrangeStart.")
}
if (len(subrangeEnd) != 10){
return nil, errors.New("splitRangeIntoEqualSubranges returning invalid subrangeObject with invalid length subrangeEnd.")
}
subrangeStartArray := [10]byte(subrangeStart)
subrangeEndArray := [10]byte(subrangeEnd)
inboxSubrangeObject := InboxSubrange{
SubrangeStart: subrangeStartArray,
SubrangeEnd: subrangeEndArray,
}
inboxSubrangesList = append(inboxSubrangesList, inboxSubrangeObject)
}
return inboxSubrangesList, nil
}
type SubrangeObject struct{
SubrangeStart []byte
SubrangeEnd []byte
}
//Outputs:
// -[]subrangeObject
// -error
func splitRangeIntoEqualSubranges(subrangeBoundBytesLength int, fullRangeStart []byte, fullRangeEnd []byte, fullRangeQuantity int64, maximumItemsPerSubrange int64)([]SubrangeObject, error){
if (subrangeBoundBytesLength != 10 && subrangeBoundBytesLength != 16){
return nil, errors.New("splitRangeIntoEqualSubranges called with invalid subrangeBoundBytesLength.")
}
if (maximumItemsPerSubrange == 0 || fullRangeQuantity == 0){
return nil, errors.New("Cannot create subranges: 0 quantity or 0 maximum items")
}
boundsAreEqual, fullRangeStartInt, fullRangeEndInt, fullRangeLength, err := getRangeLength(fullRangeStart, fullRangeEnd)
if (err != nil){ return nil, err }
if (fullRangeQuantity <= maximumItemsPerSubrange || boundsAreEqual == true){
subrangeObject := SubrangeObject{
SubrangeStart: fullRangeStart,
SubrangeEnd: fullRangeEnd,
}
newSubrangeObjectList := []SubrangeObject{subrangeObject}
return newSubrangeObjectList, nil
}
itemsPerSubrange := math.Floor(float64(fullRangeQuantity)/float64(maximumItemsPerSubrange))
if (itemsPerSubrange > 100000000){
return nil, errors.New("Items per subrange is too large.")
}
itemsPerSubrangeInt := big.NewInt(int64(itemsPerSubrange))
// SubrangeIncrement is the integer count of each subrange
getSubrangeIncrement := func()(*big.Int, error){
subrangeIncrement := new(big.Int)
subrangeIncrement.Div(fullRangeLength, itemsPerSubrangeInt)
zeroInt := big.NewInt(0)
compareResult := subrangeIncrement.Cmp(zeroInt)
if (compareResult == -1){
// This should not happen
return nil, errors.New("Subrange increment is negative.")
}
if (compareResult == 0){
// Increment is zero
// We need increment to be at least 1
oneInt := big.NewInt(1)
return oneInt, nil
}
return subrangeIncrement, nil
}
subrangeIncrement, err := getSubrangeIncrement()
if (err != nil) { return nil, err }
subrangeObjectsList := make([]SubrangeObject, 0)
index := fullRangeStartInt
for {
subrangeStart := make([]byte, subrangeBoundBytesLength)
index.FillBytes(subrangeStart)
// Outputs:
// -bool: Is the final subrange
// -*big.Int: Subrange end int
getSubrangeEndInt := func()(bool, *big.Int){
nextBound := new(big.Int)
nextBound.Add(index, subrangeIncrement)
comparisonResult := nextBound.Cmp(fullRangeEndInt)
if (comparisonResult == -1){
// This subrange will not take us to the end
// Another bound remains.
return false, nextBound
}
// This bound takes us either exactly to the end, or exceeds the final value
// Either way, return the final value
return true, fullRangeEndInt
}
isFinalSubrange, subrangeEndInt := getSubrangeEndInt()
subrangeEnd := make([]byte, subrangeBoundBytesLength)
subrangeEndInt.FillBytes(subrangeEnd)
newSubrangeObject := SubrangeObject{
SubrangeStart: subrangeStart,
SubrangeEnd: subrangeEnd,
}
subrangeObjectsList = append(subrangeObjectsList, newSubrangeObject)
if (isFinalSubrange == true){
break
}
index.Set(subrangeEndInt)
oneInt := big.NewInt(1)
index.Add(index, oneInt)
}
return subrangeObjectsList, nil
}
//Outputs:
// -bool: Slices are equal
// -bool: Latter is larger
// -error
func compareByteSlices(sliceA []byte, sliceB []byte)(bool, bool, error){
if (len(sliceA) != len(sliceB)){
return false, false, errors.New("compareByteSlices called with mismatched length slices.")
}
for index, sliceAByte := range sliceA{
sliceBByte := sliceB[index]
if (sliceAByte == sliceBByte){
continue
}
if (sliceAByte < sliceBByte){
return false, true, nil
}
return false, false, nil
}
// Slices are identical
return true, false, nil
}
//Outputs:
// -bool: Range bounds are equal
// -*big.Int: Range start
// -*big.Int: Range end
// -*big.Int: Range length (will be 1 if ranges are equal)
// -error
func getRangeLength(rangeStart []byte, rangeEnd []byte)(bool, *big.Int, *big.Int, *big.Int, error){
if (len(rangeStart) != len(rangeEnd)){
return false, nil, nil, nil, errors.New("getRangeLength called with differing length range bounds.")
}
// Range length: (absolute value of the difference between both bounds) +1
// We add 1 to avoid 0 values. A range where rangeStart == rangeEnd contains 1 value.
rangeStartInt := new(big.Int)
rangeStartInt.SetBytes(rangeStart)
rangeEndInt := new(big.Int)
rangeEndInt.SetBytes(rangeEnd)
//Outputs:
// -bool: Range bounds are equal
// -*big.Int: Lesser range bound
// -*big.Int: Greater range bound
getSmallerAndLargerRangeBounds := func()(bool, *big.Int, *big.Int){
comparisonResult := rangeStartInt.Cmp(rangeEndInt)
if (comparisonResult == -1){
return false, rangeStartInt, rangeEndInt
}
if (comparisonResult == 0){
return true, rangeStartInt, rangeEndInt
}
// comparisonResult == 1
return false, rangeEndInt, rangeStartInt
}
boundsAreEqual, smallerRangeBound, largerRangeBound := getSmallerAndLargerRangeBounds()
if (boundsAreEqual == true){
rangeLength := big.NewInt(1)
return true, smallerRangeBound, largerRangeBound, rangeLength, nil
}
result := new(big.Int)
result.Sub(largerRangeBound, smallerRangeBound)
oneInt := big.NewInt(1)
result.Add(result, oneInt)
return false, smallerRangeBound, largerRangeBound, result, nil
}