Skip to content

Commit

Permalink
Refactor spaceImage getter as getSpaceImage() async (#620)
Browse files Browse the repository at this point in the history
Should not be doing an async decrypt inside applySnapshot. Timing issue
when getting the spaceImage from the streamStateView_Space. `spaceImage`
can be undefined while the payload is being decrypted.

Instead of doing the decryption inside `applySnapshot`, store the
encrypted data only. The caller uses `await
streamStateView_Space.spaceContent.getSpaceImage()` to get the decrypted
chunked media.
  • Loading branch information
tak-hntlabs authored Aug 8, 2024
1 parent a60444b commit e5fa21b
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 48 deletions.
27 changes: 8 additions & 19 deletions packages/sdk/src/space.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ describe('spaceTests', () => {
})

// decrypt the snapshot and assert the image values
let encryptedData =
const encryptedData =
spaceStream.view.snapshot?.content.case === 'spaceContent'
? spaceStream.view.snapshot.content.value.spaceImage?.data
: undefined
Expand All @@ -175,7 +175,7 @@ describe('spaceTests', () => {
isEncryptedData(encryptedData) &&
encryptedData.algorithm === AES_GCM_DERIVED_ALGORITHM,
).toBe(true)
let decrypted = encryptedData
const decrypted = encryptedData
? await bobsClient.decryptSpaceImage(spaceId, encryptedData)
: undefined
expect(
Expand Down Expand Up @@ -208,24 +208,13 @@ describe('spaceTests', () => {
})

// decrypt the snapshot and assert the image values
encryptedData =
spaceStream.view.snapshot?.content.case === 'spaceContent'
? spaceStream.view.snapshot.content.value.spaceImage?.data
: undefined
const spaceImage = await spaceStream.view.spaceContent.getSpaceImage()
expect(
encryptedData !== undefined &&
isEncryptedData(encryptedData) &&
encryptedData.algorithm === AES_GCM_DERIVED_ALGORITHM,
).toBe(true)
decrypted = encryptedData
? await bobsClient.decryptSpaceImage(spaceId, encryptedData)
: undefined
expect(
decrypted !== undefined &&
decrypted?.info?.mimetype === image2.mimetype &&
decrypted?.info?.filename === image2.filename &&
decrypted.encryption.case === 'derived' &&
decrypted.encryption.value.context === spaceContractAddress,
spaceImage !== undefined &&
spaceImage?.info?.mimetype === image2.mimetype &&
spaceImage?.info?.filename === image2.filename &&
spaceImage.encryption.case === 'derived' &&
spaceImage.encryption.value.context === spaceContractAddress,
).toBe(true)
})
})
77 changes: 48 additions & 29 deletions packages/sdk/src/streamStateView_Space.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,17 @@ export type ParsedChannelProperties = {
export class StreamStateView_Space extends StreamStateView_AbstractContent {
readonly streamId: string
readonly spaceChannelsMetadata = new Map<string, ParsedChannelProperties>()
private _spaceImage: ChunkedMedia | undefined
private spaceImage: ChunkedMedia | undefined
private encryptedSpaceImage: EncryptedData | undefined
private decryptionInProgress:
| { encryptedData: EncryptedData; promise: Promise<ChunkedMedia | undefined> }
| undefined

constructor(streamId: string) {
super()
this.streamId = streamId
}

get spaceImage(): ChunkedMedia | undefined {
return this._spaceImage
}

private set spaceImage(value: ChunkedMedia | undefined) {
this._spaceImage = value
}

applySnapshot(
eventHash: string,
_snapshot: Snapshot,
Expand All @@ -59,13 +55,7 @@ export class StreamStateView_Space extends StreamStateView_AbstractContent {
}

if (content.spaceImage?.data) {
this.decryptSpaceImage(content.spaceImage.data)
.then((media) => {
this.spaceImage = media
})
.catch((err) => {
throw err
})
this.encryptedSpaceImage = content.spaceImage.data
}
}

Expand Down Expand Up @@ -135,13 +125,7 @@ export class StreamStateView_Space extends StreamStateView_AbstractContent {
)
break
case 'spaceImage':
this.decryptSpaceImage(payload.content.value)
.then((media) => {
this.spaceImage = media
})
.catch((err) => {
throw err
})
this.encryptedSpaceImage = payload.content.value
break
case undefined:
break
Expand All @@ -150,6 +134,47 @@ export class StreamStateView_Space extends StreamStateView_AbstractContent {
}
}

public async getSpaceImage(): Promise<ChunkedMedia | undefined> {
// if we have an encrypted space image, decrypt it
if (this.encryptedSpaceImage) {
const encryptedData = this.encryptedSpaceImage
this.encryptedSpaceImage = undefined
this.decryptionInProgress = {
promise: this.decryptSpaceImage(encryptedData),
encryptedData,
}
return this.decryptionInProgress.promise
}

// if there isn't an updated encrypted space image, but a decryption is
// in progress, return the promise
if (this.decryptionInProgress) {
return this.decryptionInProgress.promise
}

// always return the decrypted space image
return this.spaceImage
}

private async decryptSpaceImage(
encryptedData: EncryptedData,
): Promise<ChunkedMedia | undefined> {
try {
const spaceAddress = contractAddressFromSpaceId(this.streamId)
const context = spaceAddress.toLowerCase()
const plaintext = await decryptDerivedAESGCM(context, encryptedData)
const decryptedImage = ChunkedMedia.fromBinary(plaintext)
if (encryptedData === this.decryptionInProgress?.encryptedData) {
this.spaceImage = decryptedImage
}
return decryptedImage
} finally {
if (encryptedData === this.decryptionInProgress?.encryptedData) {
this.decryptionInProgress = undefined
}
}
}

private addSpacePayload_UpdateChannelAutojoin(
payload: SpacePayload_UpdateChannelAutojoin,
stateEmitter: TypedEmitter<StreamStateEvents> | undefined,
Expand Down Expand Up @@ -189,12 +214,6 @@ export class StreamStateView_Space extends StreamStateView_AbstractContent {
)
}

private async decryptSpaceImage(encryptedImage: EncryptedData): Promise<ChunkedMedia> {
const keyPhrase = contractAddressFromSpaceId(this.streamId)
const plaintext = await decryptDerivedAESGCM(keyPhrase, encryptedImage)
return ChunkedMedia.fromBinary(plaintext)
}

private addSpacePayload_Channel(
_eventHash: string,
payload: SpacePayload_ChannelMetadata | SpacePayload_ChannelUpdate,
Expand Down

0 comments on commit e5fa21b

Please sign in to comment.