Skip to content

Commit

Permalink
Merge pull request #1 from earthstar-project/successor-fixes
Browse files Browse the repository at this point in the history
fix: successorPath, successorPrefix had wrong behaviour
  • Loading branch information
sgwilym authored Jul 3, 2024
2 parents 0c7323b + 051fb7a commit 96530db
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 55 deletions.
5 changes: 5 additions & 0 deletions src/areas/areas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ export function areaTo3dRange<SubspaceType>(
start: area.pathPrefix,
end: successorPrefix(
area.pathPrefix,
{
maxComponentCount: opts.maxComponentCount,
maxComponentLength: opts.maxPathComponentLength,
maxPathLength: opts.maxPathLength,
},
) || OPEN_END,
},
};
Expand Down
38 changes: 29 additions & 9 deletions src/order/successor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,44 @@ const maxComponentLength = 2;
const testVectorsSuccessorPath: VectorSuccessorPath[] = [
[
[],
[new Uint8Array(1)],
[new Uint8Array([])],
],
[
[new Uint8Array([0])],
[new Uint8Array([0, 0])],
[new Uint8Array([0]), new Uint8Array([])],
],

[
[new Uint8Array([0, 0])],
[new Uint8Array([0, 1])],
[new Uint8Array([0, 0]), new Uint8Array([])],
],

[
[new Uint8Array([0, 0]), new Uint8Array([0])],
[new Uint8Array([0, 0]), new Uint8Array([0]), new Uint8Array()],
],

[
[new Uint8Array([0, 0]), new Uint8Array([0]), new Uint8Array()],
[new Uint8Array([0, 0]), new Uint8Array([1])],
],

[
[new Uint8Array([0, 0]), new Uint8Array([255])],
[new Uint8Array([0, 0]), new Uint8Array([255]), new Uint8Array([])],
[new Uint8Array([0, 1])],
],

[
[new Uint8Array([0, 255]), new Uint8Array([0])],
[new Uint8Array([0, 255]), new Uint8Array([0]), new Uint8Array()],
[new Uint8Array([0, 255]), new Uint8Array([1])],
],
[
[new Uint8Array([0]), new Uint8Array([0]), new Uint8Array([0])],
[new Uint8Array([0]), new Uint8Array([0]), new Uint8Array([1])],
],

[
[new Uint8Array([255]), new Uint8Array([255]), new Uint8Array([255])],
[new Uint8Array([255, 255]), new Uint8Array([255]), new Uint8Array([])],
null,
],
];
Expand Down Expand Up @@ -68,45 +78,54 @@ const testVectorsSuccessorPrefix: VectorSuccessorPrefix[] = [
[],
null,
],

[
[new Uint8Array()],
[new Uint8Array([0])],
],

[
[new Uint8Array([1]), new Uint8Array([])],
[new Uint8Array([1]), new Uint8Array([0])],
],

[
[new Uint8Array([0])],
[new Uint8Array([1])],
[new Uint8Array([0, 0])],
],

[
[new Uint8Array([0, 0])],
[new Uint8Array([0, 1])],
],

[
[new Uint8Array([0, 0]), new Uint8Array([0])],
[new Uint8Array([0, 0]), new Uint8Array([1])],
],

[
[new Uint8Array([0, 0]), new Uint8Array([255])],
[new Uint8Array([0, 1])],
],

[
[new Uint8Array([0, 255]), new Uint8Array([0])],
[new Uint8Array([0, 255]), new Uint8Array([1])],
],

[
[new Uint8Array([0]), new Uint8Array([0]), new Uint8Array([0])],
[new Uint8Array([0]), new Uint8Array([0]), new Uint8Array([1])],
],

[
[new Uint8Array([255]), new Uint8Array([255]), new Uint8Array([255])],
[new Uint8Array([255, 255]), new Uint8Array([255])],
null,
],
];

Deno.test("successorPrefix", () => {
Deno.test.only("successorPrefix", () => {
for (const [original, successor] of testVectorsSuccessorPrefix) {
if (successor) {
assert(
Expand All @@ -117,6 +136,7 @@ Deno.test("successorPrefix", () => {
assertEquals(
successorPrefix(
original,
{ maxComponentCount, maxComponentLength, maxPathLength },
),
successor,
);
Expand Down
97 changes: 52 additions & 45 deletions src/order/successor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import type { PathScheme } from "../parameters/types.ts";
import { isValidPath } from "../paths/paths.ts";
import type { Path } from "../paths/types.ts";

/** Returns the successor to a path given a `Path` and `PathScheme`. */
/** Return the least path which is greater than `path`, or return `null` if `path` is the greatest possible path. */
export function successorPath(
path: Path,
{ maxComponentCount, maxComponentLength, maxPathLength }: PathScheme,
): Path | null {
if (path.length === 0) {
const nextPath = [new Uint8Array(1)];
const nextPath = [new Uint8Array()];

const isSimplestOptionValid = isValidPath(
nextPath,
Expand All @@ -22,17 +22,26 @@ export function successorPath(
return null;
}

const workingPath = [...path];
// Try add an empty component
const nextPath = [...path, new Uint8Array()];

const isSimplestOptionValid = isValidPath(
nextPath,
{ maxComponentCount, maxComponentLength, maxPathLength },
);

if (isSimplestOptionValid) {
return nextPath;
}

for (let i = path.length - 1; i >= 0; i--) {
// Does the simplest thing work?

const component = workingPath[i];
const component = path[i];

const simplestNextComponent = new Uint8Array([...component, 0]);

const simplestNextPath = [...path];
simplestNextPath[i] = simplestNextComponent;
const simplestNextPath = [...path.slice(0, i), simplestNextComponent];

const isSimplestOptionValid = isValidPath(
simplestNextPath,
Expand All @@ -48,65 +57,63 @@ export function successorPath(
const incrementedComponent = successorBytesFixedWidth(component);

if (incrementedComponent) {
const nextPath = [...path.slice(0, i)];
nextPath[i] = incrementedComponent;
const nextPath = [...path.slice(0, i), incrementedComponent];

return nextPath;
}

// Otherwise (there was an overflow)...

workingPath.pop();
}

if (workingPath.length === 0) {
return null;
}

return workingPath;
return null;
}

/** Return a successor to a prefix, that is, the next element that is not a prefix of the given path. */
/** Return the least path that is greater than `path` and which is not prefixed by `path`, or `null` if `path` is the empty path *or* if `path` is the greatest path. */
export function successorPrefix(
path: Path,
{ maxComponentCount, maxComponentLength, maxPathLength }: PathScheme,
): Path | null {
if (path.length === 0) {
return null;
}

const workingPath = [...path];

for (let i = path.length - 1; i >= 0; i--) {
// Does the simplest thing work?
const component = path[i];

const component = workingPath[i];
const simplestNextComponent = new Uint8Array([...component, 0]);

const incrementedComponent = successorBytesFixedWidth(component);
const simplestNextPath = [...path.slice(0, i), simplestNextComponent];

if (incrementedComponent) {
const nextPath = [...path.slice(0, i)];
nextPath[i] = incrementedComponent;
const isSimplestOptionValid = isValidPath(
simplestNextPath,
{ maxComponentCount, maxComponentLength, maxPathLength },
);

return nextPath;
if (isSimplestOptionValid) {
return simplestNextPath;
}

// Otherwise (there was an overflow)...

if (component.byteLength === 0) {
const nextPath = [...path.slice(0, i)];
nextPath[i] = new Uint8Array([0]);

return nextPath;
// prefix_successor

for (let ci = component.length - 1; ci >= 0; ci--) {
const byte = component[ci];

if (byte !== 255) {
const newComponent = new Uint8Array([
...component.slice(0, ci),
byte + 1,
]);

const newPath = [...path.slice(0, i), newComponent];

if (
isValidPath(newPath, {
maxComponentCount,
maxComponentLength,
maxPathLength,
})
) {
return newPath;
}
}
}

workingPath.pop();
}

if (workingPath.length === 0) {
return null;
}

return workingPath;
return null;
}

/** Return the succeeding bytestring of the given bytestring without increasing that bytestring's length. */
Expand Down
6 changes: 5 additions & 1 deletion src/parameters/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ export type KeypairEncodingScheme<PublicKey, Signature> = {

/** A scheme for signing and verifying data using key pairs. */
export type SignatureScheme<PublicKey, SecretKey, Signature> = {
sign: (publicKey: PublicKey, secretKey: SecretKey, bytestring: Uint8Array) => Promise<Signature>;
sign: (
publicKey: PublicKey,
secretKey: SecretKey,
bytestring: Uint8Array,
) => Promise<Signature>;
verify: (
publicKey: PublicKey,
signature: Signature,
Expand Down

0 comments on commit 96530db

Please sign in to comment.