host-protocol.spec.ts 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import { describe, expect, it } from 'vitest'
  2. import {
  3. DesktopHostRequestDecoder,
  4. encodeDesktopResponseData,
  5. encodeDesktopResponseEnd,
  6. encodeDesktopResponseError,
  7. encodeDesktopResponseStart,
  8. } from '../../desktop-host/src/wire.ts'
  9. import {
  10. DesktopHostResponseDecoder,
  11. encodeDesktopRequestCancel,
  12. encodeDesktopRequestData,
  13. encodeDesktopRequestEnd,
  14. encodeDesktopRequestStart,
  15. } from '../src/host-protocol.ts'
  16. function decodeInPieces<T>(bytes: Buffer, push: (chunk: Buffer) => readonly T[]): T[] {
  17. const values: T[] = []
  18. for (let offset = 0; offset < bytes.byteLength; offset += 7) {
  19. values.push(...push(bytes.subarray(offset, offset + 7)))
  20. }
  21. return values
  22. }
  23. describe('desktop Host pipe protocol', () => {
  24. it('keeps Electron request frames compatible with the installed Host decoder', () => {
  25. const decoder = new DesktopHostRequestDecoder()
  26. const bytes = Buffer.concat([
  27. encodeDesktopRequestStart(7, {
  28. url: 'dsh-app://app/api/session',
  29. method: 'POST',
  30. headers: [['content-type', 'application/json']],
  31. hasBody: true,
  32. }),
  33. encodeDesktopRequestData(7, Buffer.from('{"ok":true}')),
  34. encodeDesktopRequestEnd(7),
  35. encodeDesktopRequestCancel(7),
  36. ])
  37. expect(decodeInPieces(bytes, chunk => decoder.push(chunk))).toEqual([
  38. {
  39. type: 'start',
  40. streamId: 7,
  41. url: 'dsh-app://app/api/session',
  42. method: 'POST',
  43. headers: [['content-type', 'application/json']],
  44. hasBody: true,
  45. },
  46. { type: 'data', streamId: 7, data: Buffer.from('{"ok":true}') },
  47. { type: 'end', streamId: 7 },
  48. { type: 'cancel', streamId: 7 },
  49. ])
  50. expect(() => { decoder.finish() }).not.toThrow()
  51. })
  52. it('keeps Host response frames compatible with the Electron decoder', () => {
  53. const decoder = new DesktopHostResponseDecoder()
  54. const bytes = Buffer.concat([
  55. encodeDesktopResponseStart(9, {
  56. status: 201,
  57. headers: [['content-type', 'application/octet-stream']],
  58. hasBody: true,
  59. }),
  60. encodeDesktopResponseData(9, Buffer.from([0, 1, 2, 255])),
  61. encodeDesktopResponseEnd(9),
  62. encodeDesktopResponseError(10, 'failed'),
  63. ])
  64. expect(decodeInPieces(bytes, chunk => decoder.push(chunk))).toEqual([
  65. {
  66. type: 'start',
  67. streamId: 9,
  68. status: 201,
  69. headers: [['content-type', 'application/octet-stream']],
  70. hasBody: true,
  71. },
  72. { type: 'data', streamId: 9, data: Buffer.from([0, 1, 2, 255]) },
  73. { type: 'end', streamId: 9 },
  74. { type: 'error', streamId: 10, message: 'failed' },
  75. ])
  76. expect(() => { decoder.finish() }).not.toThrow()
  77. })
  78. it('rejects a corrupt marker and truncated EOF on both directions', () => {
  79. const request = new DesktopHostRequestDecoder()
  80. const response = new DesktopHostResponseDecoder()
  81. expect(() => request.push(Buffer.alloc(13))).toThrow(/request frame marker/u)
  82. expect(() => response.push(Buffer.alloc(13))).toThrow(/response frame marker/u)
  83. const partialRequest = new DesktopHostRequestDecoder()
  84. partialRequest.push(encodeDesktopRequestEnd(1).subarray(0, 5))
  85. expect(() => { partialRequest.finish() }).toThrow(/ended inside a frame/u)
  86. const partialResponse = new DesktopHostResponseDecoder()
  87. partialResponse.push(encodeDesktopResponseEnd(1).subarray(0, 5))
  88. expect(() => { partialResponse.finish() }).toThrow(/ended inside a frame/u)
  89. })
  90. })