Skip to content

Commit 8dc438c

Browse files
authored
Merge pull request #4 from Goddchen/release/0.0.3
Release 0.0.3
2 parents 4c6b08f + 6715837 commit 8dc438c

21 files changed

+1382
-61
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@
55
## 0.0.2
66

77
- Fix pub score issues
8+
9+
## 0.1.0
10+
11+
- Implement VncAuthentication security type (aka, using a password)

README.md

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,55 +16,55 @@ And here we are, after a weekend, with the first, minimal, protocol implementati
1616

1717
### Protocol Versions
1818

19-
- [ ] 3.3
20-
- [ ] 3.7
21-
- [X] 3.8
19+
- 3.3
20+
- 3.7
21+
- 3.8
2222

2323
### Encodings
2424

25-
- [X] Raw
26-
- [ ] CopyRect
27-
- [ ] RRE (obsolescent)
28-
- [ ] Hextile (obsolescent)
29-
- [ ] TRLE
30-
- [ ] ZRLE
31-
- [ ] Cursor pseudo-encoding
32-
- [ ] DesktopSize pseudo-encoding
25+
- Raw
26+
- CopyRect
27+
- RRE (obsolescent)
28+
- Hextile (obsolescent)
29+
- TRLE
30+
- ZRLE
31+
- Cursor pseudo-encoding
32+
- DesktopSize pseudo-encoding
3333

3434
### Security Types
3535

36-
- [X] None
37-
- [ ] VNC Authentication
36+
- None
37+
- VNC Authentication
3838

3939
### Pixel Formats
4040

41-
- [X] BGRA8888 (https://api.flutter.dev/flutter/dart-ui/PixelFormat.html#bgra8888, 32 bits per pixel, true-color)
41+
- BGRA8888 (https://api.flutter.dev/flutter/dart-ui/PixelFormat.html#bgra8888, 32 bits per pixel, true-color)
4242

4343
### Protocol Messages
4444

45-
- [X] ProtocolVersion handshake
46-
- [X] Security handshake
47-
- [X] SecurityResult handshake
48-
- [X] ClientInit
49-
- [X] ServerInit
45+
- ProtocolVersion handshake
46+
- Security handshake
47+
- SecurityResult handshake
48+
- ClientInit
49+
- ServerInit
5050
- Client-to-Server
51-
- [ ] SetPixelFormat
52-
- [ ] SetEncodings
53-
- [X] FramebufferUpdateRequest
54-
- [ ] KeyEvent
55-
- [ ] PointerEvent
56-
- [ ] ClientCutText
51+
- SetPixelFormat
52+
- SetEncodings
53+
- FramebufferUpdateRequest
54+
- KeyEvent
55+
- PointerEvent
56+
- ClientCutText
5757
- Server-to-Client
58-
- [X] FramebufferUpdate
59-
- [ ] SetColorMapEntries
60-
- [ ] Bell
61-
- [ ] ServerCutText
58+
- FramebufferUpdate
59+
- SetColorMapEntries
60+
- Bell
61+
- ServerCutText
6262

6363
## Installation
6464

6565
As simple as `dart pub add dart_rfb`.
6666

67-
Or manually add `dart_rfb: ^0.0.2` to your `pubspec.yaml`.
67+
Or manually add `dart_rfb: ^0.1.0` to your `pubspec.yaml`.
6868

6969
## Usage
7070

@@ -82,4 +82,4 @@ client.requestUpdate();
8282

8383
## Learn More
8484

85-
https://www.rfc-editor.org/rfc/rfc6143
85+
https://www.rfc-editor.org/rfc/rfc6143

lib/src/client/remote_frame_buffer_client.dart

Lines changed: 107 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ import 'dart:convert';
33
import 'dart:io';
44
import 'dart:typed_data';
55

6+
import 'package:dart_des/dart_des.dart';
67
import 'package:dart_rfb/src/client/config.dart';
78
import 'package:dart_rfb/src/client/remote_frame_buffer_client_update.dart';
9+
import 'package:dart_rfb/src/constants.dart';
810
import 'package:dart_rfb/src/extensions/byte_data_extensions.dart';
11+
import 'package:dart_rfb/src/extensions/int_extensions.dart';
912
import 'package:dart_rfb/src/protocol/client_init_message.dart';
1013
import 'package:dart_rfb/src/protocol/frame_buffer_update_message.dart';
1114
import 'package:dart_rfb/src/protocol/frame_buffer_update_request_message.dart';
@@ -34,6 +37,8 @@ class RemoteFrameBufferClient {
3437

3538
bool _readLoopRunning = false;
3639

40+
Option<String> _password = none();
41+
3742
/// A client that implements communication according to
3843
/// The Remote Framebuffer Protocol, aka RFC 6143, aka VNC).
3944
RemoteFrameBufferClient() {
@@ -78,13 +83,16 @@ class RemoteFrameBufferClient {
7883
);
7984
}
8085

81-
/// Connect to [hostname] on [port] and perform the protocol handshake.
86+
/// Connect to [hostname] on [port] and perform the protocol handshake,
87+
/// optionally using [password].
8288
Future<void> connect({
8389
required final String hostname,
90+
final String? password,
8491
final int port = 5900,
8592
}) async =>
8693
(await TaskEither<Object, void>.tryCatch(
8794
() async {
95+
_password = optionOf(password);
8896
_socket = some(
8997
await RawSocket.connect(
9098
hostname,
@@ -147,7 +155,7 @@ class RemoteFrameBufferClient {
147155
_readLoopRunning = true;
148156
while (_readLoopRunning) {
149157
while (socket.available() < 1) {
150-
await Future<void>.delayed(const Duration(seconds: 1));
158+
await Future<void>.delayed(Constants.socketReadWaitDuration);
151159
}
152160
final int messageType = optionOf(socket.read(1))
153161
.map(
@@ -163,7 +171,9 @@ class RemoteFrameBufferClient {
163171
switch (messageType) {
164172
case 0:
165173
while (socket.available() < 1) {
166-
await Future<void>.delayed(const Duration(seconds: 1));
174+
await Future<void>.delayed(
175+
Constants.socketReadWaitDuration,
176+
);
167177
}
168178
// read and ignore padding
169179
optionOf(socket.read(1)).getOrElse(
@@ -207,7 +217,9 @@ class RemoteFrameBufferClient {
207217
break;
208218
case 1: // SetColorMapEntries
209219
while (socket.available() < 5) {
210-
await Future<void>.delayed(const Duration(seconds: 1));
220+
await Future<void>.delayed(
221+
Constants.socketReadWaitDuration,
222+
);
211223
}
212224
final int numberOfColors = optionOf(socket.read(5))
213225
.map(
@@ -219,7 +231,9 @@ class RemoteFrameBufferClient {
219231
throw Exception('Error reading number of colors'),
220232
);
221233
while (socket.available() < numberOfColors * 6) {
222-
await Future<void>.delayed(const Duration(seconds: 1));
234+
await Future<void>.delayed(
235+
Constants.socketReadWaitDuration,
236+
);
223237
}
224238
optionOf(socket.read(numberOfColors * 6)).getOrElse(
225239
() => throw Exception('Error reading colors'),
@@ -230,7 +244,9 @@ class RemoteFrameBufferClient {
230244
break;
231245
case 3: // ServerCutText
232246
while (socket.available() < 7) {
233-
await Future<void>.delayed(const Duration(seconds: 1));
247+
await Future<void>.delayed(
248+
Constants.socketReadWaitDuration,
249+
);
234250
}
235251
final int length = optionOf(socket.read(7))
236252
.map(
@@ -241,7 +257,9 @@ class RemoteFrameBufferClient {
241257
() => throw Exception('Error reading length'),
242258
);
243259
while (socket.available() < length) {
244-
await Future<void>.delayed(const Duration(seconds: 1));
260+
await Future<void>.delayed(
261+
Constants.socketReadWaitDuration,
262+
);
245263
}
246264
optionOf(socket.read(length)).getOrElse(
247265
() => throw Exception('Error reading content'),
@@ -271,19 +289,71 @@ class RemoteFrameBufferClient {
271289
.andThen(() => _sendProtocolVersionMessage(socket: socket))
272290
.andThen(() => _readSecurityHandshake(socket: socket))
273291
.andThen(() => _sendSecurityType(socket: socket))
292+
.andThen(() => _handleSecurityType(socket: socket))
274293
.andThen(() => _readSecurityResultMessage(socket: socket))
275294
.andThen(() => _sendClientInitMessage(socket: socket))
276295
.andThen(() => _readServerInitMessage(socket: socket)),
277296
);
278297

298+
TaskEither<Object, void> _handleSecurityType({
299+
required final RawSocket socket,
300+
}) =>
301+
_password.match(
302+
() => TaskEither<Object, void>.of(null),
303+
(final String password) => TaskEither<Object, void>.tryCatch(
304+
() async {
305+
while (socket.available() < 16) {
306+
await Future<void>.delayed(Constants.socketReadWaitDuration);
307+
}
308+
final ByteData challenge = ByteData.sublistView(
309+
optionOf(socket.read(16)).getOrElse(
310+
() => throw Exception('Error reading security challenge'),
311+
),
312+
);
313+
_logger.info('< Security challenge');
314+
final ByteData encodedAndTruncatedPassword = ByteData.sublistView(
315+
Uint8List.fromList(ascii.encode(password).take(8).toList()),
316+
);
317+
final ByteData key = ByteData(8);
318+
for (int i = 0; i < 8; i++) {
319+
int byte;
320+
if (i < encodedAndTruncatedPassword.lengthInBytes) {
321+
byte = encodedAndTruncatedPassword.getUint8(i);
322+
} else {
323+
byte = 0;
324+
}
325+
byte = byte.reverseBits();
326+
key.setUint8(i, byte);
327+
}
328+
final ByteData response = ByteData.sublistView(
329+
Uint8List.fromList(
330+
DES(
331+
key: key.asUint8List(),
332+
mode: DESMode.ECB,
333+
paddingType: DESPaddingType.PKCS7,
334+
).encrypt(challenge.asUint8List()),
335+
),
336+
0,
337+
16,
338+
);
339+
_logger.info('> Security challenge response');
340+
socket.write(
341+
response.buffer
342+
.asUint8List(response.offsetInBytes, response.lengthInBytes),
343+
);
344+
},
345+
(final Object error, final _) => error,
346+
),
347+
);
348+
279349
TaskEither<Object, void> _readProtocolVersionMessage({
280350
required final RawSocket socket,
281351
}) =>
282352
TaskEither<Object, void>.tryCatch(
283353
() async {
284354
while (socket.available() <
285355
RemoteFrameBufferProtocolVersionHandshakeMessage.length) {
286-
await Future<void>.delayed(const Duration(seconds: 1));
356+
await Future<void>.delayed(Constants.socketReadWaitDuration);
287357
}
288358
final RemoteFrameBufferProtocolVersionHandshakeMessage
289359
protocolVersionHandshakeMessage =
@@ -317,7 +387,7 @@ class RemoteFrameBufferClient {
317387
TaskEither<Object, void>.tryCatch(
318388
() async {
319389
while (socket.available() < 1) {
320-
await Future<void>.delayed(const Duration(seconds: 1));
390+
await Future<void>.delayed(Constants.socketReadWaitDuration);
321391
}
322392
final int numberOfSecurityTypes = optionOf(socket.read(1)).getOrElse(
323393
() => throw Exception('Error reading number of security types'),
@@ -329,15 +399,15 @@ class RemoteFrameBufferClient {
329399
if (numberOfSecurityTypes == 0) {
330400
// Error, next 4 bytes is reason-length, then reason-string
331401
while (socket.available() < 4) {
332-
await Future<void>.delayed(const Duration(seconds: 1));
402+
await Future<void>.delayed(Constants.socketReadWaitDuration);
333403
}
334404
final int reasonLength = ByteData.sublistView(
335405
optionOf(socket.read(4)).getOrElse(
336406
() => throw Exception('Error getting reason length'),
337407
),
338408
).getUint32(0);
339409
while (socket.available() < reasonLength) {
340-
await Future<void>.delayed(const Duration(seconds: 1));
410+
await Future<void>.delayed(Constants.socketReadWaitDuration);
341411
}
342412
final String reason = optionOf(socket.read(reasonLength))
343413
.map((final Uint8List bytes) => ascii.decode(bytes))
@@ -347,7 +417,7 @@ class RemoteFrameBufferClient {
347417
);
348418
} else {
349419
while (socket.available() < numberOfSecurityTypes) {
350-
await Future<void>.delayed(const Duration(seconds: 1));
420+
await Future<void>.delayed(Constants.socketReadWaitDuration);
351421
}
352422
final RemoteFrameBufferSecurityHandshakeMessage
353423
securityResultHandshakeMessage =
@@ -364,10 +434,21 @@ class RemoteFrameBufferClient {
364434
),
365435
);
366436
_logger.log(Level.INFO, '< $securityResultHandshakeMessage');
367-
if (securityResultHandshakeMessage.securityTypes.notElem(
368-
const RemoteFrameBufferSecurityType.none(),
369-
)) {
370-
throw Exception('Server does not support security type "none"');
437+
if (_password.isNone() &&
438+
securityResultHandshakeMessage.securityTypes.notElem(
439+
const RemoteFrameBufferSecurityType.none(),
440+
)) {
441+
throw Exception(
442+
'Server does not support security type "none", but not password was provided',
443+
);
444+
}
445+
if (_password.isSome() &&
446+
securityResultHandshakeMessage.securityTypes.notElem(
447+
const RemoteFrameBufferSecurityType.vncAuthentication(),
448+
)) {
449+
throw Exception(
450+
'Server does not support security type "vncAuthentication"',
451+
);
371452
}
372453
}
373454
},
@@ -380,7 +461,7 @@ class RemoteFrameBufferClient {
380461
TaskEither<Object, void>.tryCatch(
381462
() async {
382463
while (socket.available() < 4) {
383-
await Future<void>.delayed(const Duration(seconds: 1));
464+
await Future<void>.delayed(Constants.socketReadWaitDuration);
384465
}
385466
final RemoteFrameBufferSecurityResultHandshakeMessage
386467
securityResultHandshakeMessage =
@@ -396,7 +477,7 @@ class RemoteFrameBufferClient {
396477
_logger.log(Level.INFO, '< $securityResultHandshakeMessage');
397478
if (!securityResultHandshakeMessage.success) {
398479
while (socket.available() < 4) {
399-
await Future<void>.delayed(const Duration(seconds: 1));
480+
await Future<void>.delayed(Constants.socketReadWaitDuration);
400481
}
401482
final int reasonLength = optionOf(socket.read(4))
402483
.map(
@@ -407,7 +488,7 @@ class RemoteFrameBufferClient {
407488
() => throw Exception('Error reading reason length'),
408489
);
409490
while (socket.available() < reasonLength) {
410-
await Future<void>.delayed(const Duration(seconds: 1));
491+
await Future<void>.delayed(Constants.socketReadWaitDuration);
411492
}
412493
final String reason = optionOf(socket.read(reasonLength))
413494
.map((final Uint8List bytes) => ascii.decode(bytes))
@@ -474,13 +555,16 @@ class RemoteFrameBufferClient {
474555
}) =>
475556
TaskEither<Object, void>.tryCatch(
476557
() async {
558+
final RemoteFrameBufferSecurityType securityType = _password.match(
559+
() => const RemoteFrameBufferSecurityType.none(),
560+
(final _) =>
561+
const RemoteFrameBufferSecurityType.vncAuthentication(),
562+
);
477563
_logger.log(
478564
Level.INFO,
479-
'> ${const RemoteFrameBufferSecurityType.none()}',
480-
);
481-
socket.write(
482-
const RemoteFrameBufferSecurityType.none().toBytes().asUint8List(),
565+
'> $securityType',
483566
);
567+
socket.write(securityType.toBytes().asUint8List());
484568
},
485569
(final Object error, final _) => error,
486570
);

lib/src/constants.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Constants {
2+
static const Duration socketReadWaitDuration = Duration(milliseconds: 1);
3+
}

0 commit comments

Comments
 (0)