Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 5, 2025

Implements encoder and decoder for SSH 2.0 protocol data types according to RFC 4251 specification.

Overview

This PR adds a complete SSH 2.0 codec implementation in src/ssh/ following the same patterns as existing codecs (XDR, Avro, etc.). The implementation provides SshEncoder and SshDecoder classes for encoding and decoding SSH protocol data types.

Data Types Supported

All SSH 2.0 data types from RFC 4251 Section 5 are implemented:

  • byte - 8-bit arbitrary value
  • boolean - Single byte (0 = FALSE, 1 = TRUE)
  • uint32 - 32-bit unsigned integer (big-endian/network byte order)
  • uint64 - 64-bit unsigned integer (big-endian, using JavaScript BigInt)
  • string - Length-prefixed arbitrary binary strings with multiple encoding methods:
    • writeBinStr() / readBinStr() - Binary data as Uint8Array
    • writeStr() / readStr() - UTF-8 encoded strings
    • writeAsciiStr() / readAsciiStr() - ASCII encoded strings
  • mpint - Multiple precision integers in two's complement format (via JsonPackMpint class)
  • name-list - Comma-separated list of names stored as string arrays

Usage Example

import {Writer} from '@jsonjoy.com/buffers/lib/Writer';
import {Reader} from '@jsonjoy.com/buffers/lib/Reader';
import {SshEncoder, SshDecoder} from './src/ssh';
import {JsonPackMpint} from './src/JsonPackMpint';

const writer = new Writer();
const encoder = new SshEncoder(writer);

// Encode SSH data types
encoder.writeBoolean(true);
encoder.writeUint32(12345);
encoder.writeStr('hello-ssh');
encoder.writeNameList(['diffie-hellman-group14-sha1', 'ssh-rsa']);
encoder.writeUint64(BigInt('0x123456789ABCDEF'));

const mpint = JsonPackMpint.fromBigInt(BigInt(-1234));
encoder.writeMpint(mpint);

const encoded = writer.flush();

// Decode
const reader = new Reader();
const decoder = new SshDecoder(reader);
reader.reset(encoded);

const bool = decoder.readBoolean();
const uint32 = decoder.readUint32();
const str = decoder.readStr();
const nameList = decoder.readNameList();
const uint64 = decoder.readUint64();
const mpintValue = decoder.readMpint().toBigInt();

Implementation Details

  • JsonPackMpint class - New utility class for handling SSH multiple precision integers with proper two's complement encoding/decoding for both positive and negative values
  • Big-endian encoding - All multi-byte quantities use network byte order (big-endian) as per SSH specification
  • No padding - Unlike XDR, SSH protocol does not use padding for alignment
  • Native BigInt support - uint64 and mpint types leverage JavaScript's native BigInt for handling 64-bit and arbitrary precision integers

Testing

Comprehensive test coverage with 83 tests:

  • 31 encoder tests covering all data types and edge cases
  • 26 decoder tests covering all data types and edge cases
  • 10 round-trip tests ensuring encoder/decoder compatibility
  • 16 mpint tests covering positive/negative numbers and conversions

All existing tests continue to pass (3,201 total tests).

Files Added

  • src/JsonPackMpint.ts - Multiple precision integer class
  • src/ssh/SshEncoder.ts - SSH protocol encoder
  • src/ssh/SshDecoder.ts - SSH protocol decoder
  • src/ssh/index.ts - Module exports
  • src/__tests__/JsonPackMpint.spec.ts - Mpint tests
  • src/ssh/__tests__/SshEncoder.spec.ts - Encoder tests
  • src/ssh/__tests__/SshDecoder.spec.ts - Decoder tests
  • src/ssh/__tests__/codec.spec.ts - Round-trip tests

Resolves #[issue-number]

Original prompt

This section details on the original issue you should resolve

<issue_title>Add SSH data type codec</issue_title>
<issue_description>Implement encoder and decoder for SSH 2.0 data types in src/ssh/ folder. Implement SshEncoder and SshDecoder (see XDR codec at src/xdr/ for reference).

Data type representation in JavaScript:

  • Use native JavaScript data types where possible. Represent uint32 with number, uint64 with bigint.
  • To store mpint create a class in src/JsonPackMpint.ts.
  • Store name-list in POJO string array string[].
  • byte maps to Uint8Array (.writeBin() and .readBin()).
  • SSH string will map to either JavaScript string or Uint8Array.
    • When decoding allow to specify stored format: .readBinStr(), .readStr() (UTF-8), .readAsciiStr().
    • Same in encoder: .writeBinStr(value: Uint8Array), .writeStr(value: string) (UTF-8), .writeAsciiStr(value: string).

Specification: https://datatracker.ietf.org/doc/html/rfc4251

Excerpt:

5.  Data Type Representations Used in the SSH Protocols

   byte

      A byte represents an arbitrary 8-bit value (octet).  Fixed length
      data is sometimes represented as an array of bytes, written
      byte[n], where n is the number of bytes in the array.







Ylonen & Lonvick            Standards Track                     [Page 8]

RFC 4251               SSH Protocol Architecture            January 2006


   boolean

      A boolean value is stored as a single byte.  The value 0
      represents FALSE, and the value 1 represents TRUE.  All non-zero
      values MUST be interpreted as TRUE; however, applications MUST NOT
      store values other than 0 and 1.

   uint32

      Represents a 32-bit unsigned integer.  Stored as four bytes in the
      order of decreasing significance (network byte order).  For
      example: the value 699921578 (0x29b7f4aa) is stored as 29 b7 f4
      aa.

   uint64

      Represents a 64-bit unsigned integer.  Stored as eight bytes in
      the order of decreasing significance (network byte order).

   string

      Arbitrary length binary string.  Strings are allowed to contain
      arbitrary binary data, including null characters and 8-bit
      characters.  They are stored as a uint32 containing its length
      (number of bytes that follow) and zero (= empty string) or more
      bytes that are the value of the string.  Terminating null
      characters are not used.

      Strings are also used to store text.  In that case, US-ASCII is
      used for internal names, and ISO-10646 UTF-8 for text that might
      be displayed to the user.  The terminating null character SHOULD
      NOT normally be stored in the string.  For example: the US-ASCII
      string "testing" is represented as 00 00 00 07 t e s t i n g.  The
      UTF-8 mapping does not alter the encoding of US-ASCII characters.

   mpint

      Represents multiple precision integers in two's complement format,
      stored as a string, 8 bits per byte, MSB first.  Negative numbers
      have the value 1 as the most significant bit of the first byte of
      the data partition.  If the most significant bit would be set for
      a positive number, the number MUST be preceded by a zero byte.
      Unnecessary leading bytes with the value 0 or 255 MUST NOT be
      included.  The value zero MUST be stored as a string with zero
      bytes of data.

      By convention, a number that is used in modular computations in
      Z_n SHOULD be represented in the range 0 <= x < n.



Ylonen & Lonvick            Standards Track                     [Page 9]

RFC 4251               SSH Protocol Architecture            January 2006


         Examples:

         value (hex)        representation (hex)
         -----------        --------------------
         0                  00 00 00 00
         9a378f9b2e332a7    00 00 00 08 09 a3 78 f9 b2 e3 32 a7
         80                 00 00 00 02 00 80
         -1234              00 00 00 02 ed cc
         -deadbeef          00 00 00 05 ff 21 52 41 11

   name-list

      A string containing a comma-separated list of names.  A name-list
      is represented as a uint32 containing its length (number of bytes
      that follow) followed by a comma-separated list of zero or more
      names.  A name MUST have a non-zero length, and it MUST NOT
      contain a comma (",").  As this is a list of names, all of the
      elements contained are names and MUST be in US-ASCII.  Context may
      impose additional restrictions on the names.  For example, the
      names in a name-list may have to be a list of valid algorithm
      identifiers (see Section 6 below), or a list of [RFC3066] language
      tags.  The order of the names in a name-list may or may not be
      significant.  Again, this depends on the context in which the list
      is used.  Terminating null characters MUST NOT be used, neither
      for the individual names, nor for the list as a whole.

       Examples:

...

</details>
Fixes jsonjoy-com/json-pack#57

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click [here](https://survey3.medallia.com/?EAHeSx-AP01bZqG0Ld9QLQ) to start the survey.

Copilot AI and others added 2 commits October 5, 2025 11:25
Co-authored-by: streamich <9773803+streamich@users.noreply.github.com>
Co-authored-by: streamich <9773803+streamich@users.noreply.github.com>
Copilot AI changed the title [WIP] Add SSH data type codec feat: Add SSH 2.0 data type codec with encoder and decoder Oct 5, 2025
Copilot AI requested a review from streamich October 5, 2025 11:32
Copilot finished work on behalf of streamich October 5, 2025 11:32
@streamich streamich marked this pull request as ready for review October 5, 2025 13:23
@streamich streamich merged commit b01e41f into master Oct 9, 2025
8 checks passed
@streamich streamich deleted the copilot/fix-b7ecd981-de84-4d8c-8f54-27514466ca3d branch October 9, 2025 11:50
@github-actions
Copy link

github-actions bot commented Oct 9, 2025

🎉 This PR is included in version 1.20.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants