• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LukaJCB / ts-mls / 20945201760

13 Jan 2026 04:55AM UTC coverage: 95.199% (-0.5%) from 95.727%
20945201760

push

github

web-flow
Use CiphersuiteId instead of CiphersuiteName for internal values (#200)

* Use CiphersuiteId instead of CiphersuiteName for internal values

* Use ProtocolVersionValue instead of ProtocolVersionName

* Use CredentialTypeValue instead of CredentialTypeName

* Use DefaultProposalTypeValue instead of DefaultProposalTypeName

* Remove extensionType

* Refactor credential

* Cleanup

* Use LeafNodeSourceValue insteda of Name

* Cleanup

* Update NodeType and LeafNodeSource

* Update contentType

* Update resumptionPskusage & senderTypes

* Update wireformat and pskTypes

* Update ts-mls.api

412 of 421 branches covered (97.86%)

Branch coverage included in aggregate %.

193 of 208 new or added lines in 35 files covered. (92.79%)

6 existing lines in 3 files now uncovered.

2364 of 2495 relevant lines covered (94.75%)

75088.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.18
/src/publicMessage.ts
1
import { Decoder, flatMapDecoder, mapDecoder, mapDecoders, succeedDecoder } from "./codec/tlsDecoder.js"
2
import { contramapBufferEncoders, BufferEncoder, encode, Encoder, encVoid } from "./codec/tlsEncoder.js"
3
import { decodeVarLenData, varLenDataEncoder } from "./codec/variableLength.js"
4
import { Extension } from "./extension.js"
5
import { decodeExternalSender, ExternalSender } from "./externalSender.js"
6
import {
7
  decodeFramedContent,
8
  decodeFramedContentAuthData,
9
  framedContentEncoder,
10
  framedContentAuthDataEncoder,
11
  FramedContent,
12
  FramedContentAuthData,
13
} from "./framedContent.js"
14
import { GroupContext } from "./groupContext.js"
15
import { CodecError, ValidationError } from "./mlsError.js"
16
import { defaultProposalTypes } from "./defaultProposalType.js"
17
import { defaultExtensionTypes } from "./defaultExtensionType.js"
18
import { getSignaturePublicKeyFromLeafIndex, RatchetTree } from "./ratchetTree.js"
19
import { senderTypes, SenderTypeValue } from "./sender.js"
20
import { toLeafIndex } from "./treemath.js"
21
import { isDefaultProposal } from "./proposal.js"
22
import { contentTypes } from "./contentType.js"
23

24
/** @public */
25
export type PublicMessageInfo = PublicMessageInfoMember | PublicMessageInfoMemberOther
26
/** @public */
27
export type PublicMessageInfoMember = { senderType: typeof senderTypes.member; membershipTag: Uint8Array }
28
/** @public */
29
export type PublicMessageInfoMemberOther = { senderType: Exclude<SenderTypeValue, typeof senderTypes.member> }
30

31
export const publicMessageInfoEncoder: BufferEncoder<PublicMessageInfo> = (info) => {
3✔
32
  switch (info.senderType) {
903✔
33
    case senderTypes.member:
34
      return varLenDataEncoder(info.membershipTag)
902✔
35
    case senderTypes.external:
36
    case senderTypes.new_member_proposal:
37
    case senderTypes.new_member_commit:
38
      return encVoid
1✔
39
  }
40
}
41

42
export const encodePublicMessageInfo: Encoder<PublicMessageInfo> = encode(publicMessageInfoEncoder)
3✔
43

44
export function decodePublicMessageInfo(senderType: SenderTypeValue): Decoder<PublicMessageInfo> {
45
  switch (senderType) {
2,925✔
46
    case senderTypes.member:
47
      return mapDecoder(decodeVarLenData, (membershipTag) => ({
2,924✔
48
        senderType,
49
        membershipTag,
50
      }))
51
    case senderTypes.external:
52
    case senderTypes.new_member_proposal:
53
    case senderTypes.new_member_commit:
54
      return succeedDecoder({ senderType })
1✔
55
  }
56
}
57

58
/** @public */
59
export type PublicMessage = { content: FramedContent; auth: FramedContentAuthData } & PublicMessageInfo
60
export type MemberPublicMessage = PublicMessage & PublicMessageInfoMember
61
export type ExternalPublicMessage = PublicMessage & PublicMessageInfoMemberOther
62

63
export const publicMessageEncoder: BufferEncoder<PublicMessage> = contramapBufferEncoders(
3✔
64
  [framedContentEncoder, framedContentAuthDataEncoder, publicMessageInfoEncoder],
65
  (msg) => [msg.content, msg.auth, msg] as const,
903✔
66
)
67

68
export const encodePublicMessage: Encoder<PublicMessage> = encode(publicMessageEncoder)
3✔
69

70
export const decodePublicMessage: Decoder<PublicMessage> = flatMapDecoder(decodeFramedContent, (content) =>
2,925✔
71
  mapDecoders(
72
    [decodeFramedContentAuthData(content.contentType), decodePublicMessageInfo(content.sender.senderType)],
73
    (auth, info) => ({
2,925✔
74
      ...info,
75
      content,
76
      auth,
77
    }),
78
  ),
79
)
80

81
export function findSignaturePublicKey(
82
  ratchetTree: RatchetTree,
83
  groupContext: GroupContext,
84
  framedContent: FramedContent,
85
): Uint8Array {
86
  switch (framedContent.sender.senderType) {
2,274✔
87
    case senderTypes.member:
88
      return getSignaturePublicKeyFromLeafIndex(ratchetTree, toLeafIndex(framedContent.sender.leafIndex))
2,122✔
89
    case senderTypes.external: {
90
      const sender = senderFromExtension(groupContext.extensions, framedContent.sender.senderIndex)
38✔
91
      if (sender === undefined) throw new ValidationError("Received external but no external_sender extension")
38✔
92
      return sender.signaturePublicKey
38✔
93
    }
94
    case senderTypes.new_member_proposal:
95
      if (framedContent.contentType !== contentTypes.proposal)
38✔
UNCOV
96
        throw new ValidationError("Received new_member_proposal but contentType is not proposal")
×
97
      if (
38✔
98
        !isDefaultProposal(framedContent.proposal) ||
99
        framedContent.proposal.proposalType !== defaultProposalTypes.add
100
      )
UNCOV
101
        throw new ValidationError("Received new_member_proposal but proposalType was not add")
×
102

103
      return framedContent.proposal.add.keyPackage.leafNode.signaturePublicKey
38✔
104
    case senderTypes.new_member_commit: {
105
      if (framedContent.contentType !== contentTypes.commit)
76✔
UNCOV
106
        throw new ValidationError("Received new_member_commit but contentType is not commit")
×
107

108
      if (framedContent.commit.path === undefined) throw new ValidationError("Commit contains no update path")
76✔
109
      return framedContent.commit.path.leafNode.signaturePublicKey
76✔
110
    }
111
  }
112
}
113

114
export function senderFromExtension(extensions: Extension[], senderIndex: number): ExternalSender | undefined {
115
  const externalSenderExtensions = extensions.filter(
38✔
116
    (ex) => ex.extensionType === defaultExtensionTypes.external_senders,
38✔
117
  )
118

119
  const externalSenderExtension = externalSenderExtensions[senderIndex]
38✔
120

121
  if (externalSenderExtension !== undefined) {
38✔
122
    const externalSender = decodeExternalSender(externalSenderExtension.extensionData, 0)
38✔
123
    if (externalSender === undefined) throw new CodecError("Could not decode ExternalSender")
38✔
124

125
    return externalSender[0]
38✔
126
  }
127
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc