5 changed files with 199 additions and 173 deletions
			
			
		@ -1,175 +1,5 @@ | 
				
			|||||||
library argon2_ffi_base; | 
					library argon2_ffi_base; | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'dart:convert'; | 
					export 'package:argon2_ffi_base/src/argon2_ffi_base.dart'; | 
				
			||||||
import 'dart:ffi'; | 
					export 'package:argon2_ffi_base/src/argon2_ffi_noop.dart' | 
				
			||||||
import 'dart:io'; | 
					    if (dart.library.io) 'package:argon2_ffi_base/src/argon2_ffi_impl.dart'; | 
				
			||||||
import 'dart:typed_data'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import 'package:ffi/ffi.dart'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ignore_for_file: non_constant_identifier_names, | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import 'package:logging/logging.dart'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
final _logger = Logger('argon2_ffi_base'); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
typedef Argon2HashNative = Pointer<Utf8> Function( | 
					 | 
				
			||||||
  Pointer<Uint8> key, | 
					 | 
				
			||||||
  Uint32 keyLen, | 
					 | 
				
			||||||
  Pointer<Uint8> salt, | 
					 | 
				
			||||||
  Uint32 saltlen, | 
					 | 
				
			||||||
  Uint32 m_cost, // memory cost | 
					 | 
				
			||||||
  Uint32 t_cost, // time cost (number iterations) | 
					 | 
				
			||||||
  Uint32 parallelism, | 
					 | 
				
			||||||
  IntPtr hashlen, | 
					 | 
				
			||||||
  Uint8 type, | 
					 | 
				
			||||||
  Uint32 version, | 
					 | 
				
			||||||
); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
typedef Argon2Hash = Pointer<Utf8> Function( | 
					 | 
				
			||||||
  Pointer<Uint8> key, | 
					 | 
				
			||||||
  int keyLen, | 
					 | 
				
			||||||
  Pointer<Uint8> salt, | 
					 | 
				
			||||||
  int saltlen, | 
					 | 
				
			||||||
  int m_cost, // memory cost | 
					 | 
				
			||||||
  int t_cost, // time cost (number iterations) | 
					 | 
				
			||||||
  int parallelism, | 
					 | 
				
			||||||
  int hashlen, | 
					 | 
				
			||||||
  int type, | 
					 | 
				
			||||||
  int version, | 
					 | 
				
			||||||
); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
typedef ResolveLibrary = String Function(String baseName); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Argon2FfiFlutter extends Argon2Base { | 
					 | 
				
			||||||
  Argon2FfiFlutter({this.resolveLibrary}) { | 
					 | 
				
			||||||
    final argon2lib = _loadLib(); | 
					 | 
				
			||||||
    _nativeAdd = argon2lib | 
					 | 
				
			||||||
        .lookup<NativeFunction<Int32 Function(Int32, Int32)>>('native_add') | 
					 | 
				
			||||||
        .asFunction(); | 
					 | 
				
			||||||
    argon2hash = argon2lib | 
					 | 
				
			||||||
        .lookup<NativeFunction<Argon2HashNative>>('hp_argon2_hash') | 
					 | 
				
			||||||
        .asFunction(); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static ResolveLibrary defaultResolveLibrary = (name) => name; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// forces loading of dynamic library on MacOS instead of assuming | 
					 | 
				
			||||||
  /// argon2 was statically linked. (ie. flutter usage, vs dart usage) | 
					 | 
				
			||||||
  static bool resolveLibraryForceDynamic = false; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  final ResolveLibrary resolveLibrary; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int Function(int x, int y) _nativeAdd; | 
					 | 
				
			||||||
  @override | 
					 | 
				
			||||||
  Argon2Hash argon2hash; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int addIt(int x, int y) => _nativeAdd(x, y); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  DynamicLibrary _loadLib() { | 
					 | 
				
			||||||
    final resolveLibrary = this.resolveLibrary ?? defaultResolveLibrary; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!resolveLibraryForceDynamic && (Platform.isIOS || Platform.isMacOS)) { | 
					 | 
				
			||||||
      return DynamicLibrary.executable(); | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
    final libraryNames = [ | 
					 | 
				
			||||||
      [Platform.isAndroid, 'libargon2_ffi.so'], | 
					 | 
				
			||||||
      [Platform.isLinux, './libargon2_ffi_plugin.so'], | 
					 | 
				
			||||||
      [Platform.isWindows, 'argon2_ffi_plugin.dll'], | 
					 | 
				
			||||||
      [Platform.isMacOS, 'libargon2_ffi.dylib'], | 
					 | 
				
			||||||
      [Platform.isIOS, null], // only supports static linking. | 
					 | 
				
			||||||
    ]; | 
					 | 
				
			||||||
    final libraryName = libraryNames.firstWhere((element) => element[0] == true, | 
					 | 
				
			||||||
            orElse: () => throw StateError( | 
					 | 
				
			||||||
                'Unsupported Operating System ${Platform.operatingSystem}'))[1] | 
					 | 
				
			||||||
        as String; | 
					 | 
				
			||||||
    final path = resolveLibrary(libraryName); | 
					 | 
				
			||||||
    try { | 
					 | 
				
			||||||
      return DynamicLibrary.open(libraryName); | 
					 | 
				
			||||||
    } on ArgumentError catch (e, stackTrace) { | 
					 | 
				
			||||||
      _logger.severe( | 
					 | 
				
			||||||
          'Error while loading dynamic library from $path ($libraryName)', | 
					 | 
				
			||||||
          e, | 
					 | 
				
			||||||
          stackTrace); | 
					 | 
				
			||||||
      rethrow; | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
abstract class Argon2 { | 
					 | 
				
			||||||
  Uint8List argon2(Argon2Arguments args); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<Uint8List> argon2Async(Argon2Arguments args); | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Argon2Arguments { | 
					 | 
				
			||||||
  Argon2Arguments(this.key, this.salt, this.memory, this.iterations, | 
					 | 
				
			||||||
      this.length, this.parallelism, this.type, this.version); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  final Uint8List key; | 
					 | 
				
			||||||
  final Uint8List salt; | 
					 | 
				
			||||||
  final int memory; | 
					 | 
				
			||||||
  final int iterations; | 
					 | 
				
			||||||
  final int length; | 
					 | 
				
			||||||
  final int parallelism; | 
					 | 
				
			||||||
  final int type; | 
					 | 
				
			||||||
  final int version; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
abstract class Argon2Base extends Argon2 { | 
					 | 
				
			||||||
//  @protected | 
					 | 
				
			||||||
  Argon2Hash get argon2hash; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override | 
					 | 
				
			||||||
  Uint8List argon2(Argon2Arguments args) { | 
					 | 
				
			||||||
    final keyPtr = Uint8ArrayUtils.toPointer(args.key); | 
					 | 
				
			||||||
    final saltPtr = Uint8ArrayUtils.toPointer(args.salt); | 
					 | 
				
			||||||
//    final saltArray = allocate<Uint8>(count: args.salt.length); | 
					 | 
				
			||||||
//    final saltList = saltArray.asTypedList(args.length); | 
					 | 
				
			||||||
//    saltList.setAll(0, args.salt); | 
					 | 
				
			||||||
//    const memoryCost = 1 << 16; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//    _logger.fine('saltArray: ${ByteUtils.toHexList(saltArray.view)}'); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    final result = argon2hash( | 
					 | 
				
			||||||
      keyPtr, | 
					 | 
				
			||||||
      args.key.length, | 
					 | 
				
			||||||
      saltPtr, | 
					 | 
				
			||||||
      args.salt.length, | 
					 | 
				
			||||||
      args.memory, | 
					 | 
				
			||||||
      args.iterations, | 
					 | 
				
			||||||
      args.parallelism, | 
					 | 
				
			||||||
      args.length, | 
					 | 
				
			||||||
      args.type, | 
					 | 
				
			||||||
      args.version, | 
					 | 
				
			||||||
    ); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    free(keyPtr); | 
					 | 
				
			||||||
    free(saltPtr); | 
					 | 
				
			||||||
//    free(saltArray); | 
					 | 
				
			||||||
    final resultString = Utf8.fromUtf8(result); | 
					 | 
				
			||||||
    return base64.decode(resultString); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override | 
					 | 
				
			||||||
  Future<Uint8List> argon2Async(Argon2Arguments args) async { | 
					 | 
				
			||||||
    return argon2(args); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// from https://github.com/hanabi1224/flutter_native_extensions/blob/master/src/compression/dart_native_compression/lib/utils/uint8_list_utils.dart | 
					 | 
				
			||||||
class Uint8ArrayUtils { | 
					 | 
				
			||||||
  static Uint8List fromPointer(Pointer<Uint8> ptr, int length) { | 
					 | 
				
			||||||
    final view = ptr.asTypedList(length); | 
					 | 
				
			||||||
    final builder = BytesBuilder(copy: false); | 
					 | 
				
			||||||
    builder.add(view); | 
					 | 
				
			||||||
    return builder.takeBytes(); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static Pointer<Uint8> toPointer(Uint8List bytes) { | 
					 | 
				
			||||||
    final ptr = allocate<Uint8>(count: bytes.length); | 
					 | 
				
			||||||
    final byteList = ptr.asTypedList(bytes.length); | 
					 | 
				
			||||||
    byteList.setAll(0, bytes); | 
					 | 
				
			||||||
    return ptr.cast(); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,21 @@ | 
				
			|||||||
 | 
					import 'dart:typed_data'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					abstract class Argon2 { | 
				
			||||||
 | 
					  Uint8List argon2(Argon2Arguments args); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<Uint8List> argon2Async(Argon2Arguments args); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Argon2Arguments { | 
				
			||||||
 | 
					  Argon2Arguments(this.key, this.salt, this.memory, this.iterations, | 
				
			||||||
 | 
					      this.length, this.parallelism, this.type, this.version); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final Uint8List key; | 
				
			||||||
 | 
					  final Uint8List salt; | 
				
			||||||
 | 
					  final int memory; | 
				
			||||||
 | 
					  final int iterations; | 
				
			||||||
 | 
					  final int length; | 
				
			||||||
 | 
					  final int parallelism; | 
				
			||||||
 | 
					  final int type; | 
				
			||||||
 | 
					  final int version; | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,137 @@ | 
				
			|||||||
 | 
					import 'dart:convert'; | 
				
			||||||
 | 
					import 'dart:ffi'; | 
				
			||||||
 | 
					import 'dart:io'; | 
				
			||||||
 | 
					import 'dart:typed_data'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:argon2_ffi_base/src/argon2_ffi_base.dart'; | 
				
			||||||
 | 
					import 'package:argon2_ffi_base/src/utils.dart'; | 
				
			||||||
 | 
					import 'package:ffi/ffi.dart'; | 
				
			||||||
 | 
					import 'package:logging/logging.dart'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final _logger = Logger('argon2_ffi_base'); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ignore_for_file: non_constant_identifier_names, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef Argon2HashNative = Pointer<Utf8> Function( | 
				
			||||||
 | 
					  Pointer<Uint8> key, | 
				
			||||||
 | 
					  Uint32 keyLen, | 
				
			||||||
 | 
					  Pointer<Uint8> salt, | 
				
			||||||
 | 
					  Uint32 saltlen, | 
				
			||||||
 | 
					  Uint32 m_cost, // memory cost | 
				
			||||||
 | 
					  Uint32 t_cost, // time cost (number iterations) | 
				
			||||||
 | 
					  Uint32 parallelism, | 
				
			||||||
 | 
					  IntPtr hashlen, | 
				
			||||||
 | 
					  Uint8 type, | 
				
			||||||
 | 
					  Uint32 version, | 
				
			||||||
 | 
					); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef Argon2Hash = Pointer<Utf8> Function( | 
				
			||||||
 | 
					  Pointer<Uint8> key, | 
				
			||||||
 | 
					  int keyLen, | 
				
			||||||
 | 
					  Pointer<Uint8> salt, | 
				
			||||||
 | 
					  int saltlen, | 
				
			||||||
 | 
					  int m_cost, // memory cost | 
				
			||||||
 | 
					  int t_cost, // time cost (number iterations) | 
				
			||||||
 | 
					  int parallelism, | 
				
			||||||
 | 
					  int hashlen, | 
				
			||||||
 | 
					  int type, | 
				
			||||||
 | 
					  int version, | 
				
			||||||
 | 
					); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef ResolveLibrary = String Function(String baseName); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					abstract class Argon2Base extends Argon2 { | 
				
			||||||
 | 
					//  @protected | 
				
			||||||
 | 
					  Argon2Hash get argon2hash; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override | 
				
			||||||
 | 
					  Uint8List argon2(Argon2Arguments args) { | 
				
			||||||
 | 
					    final keyPtr = Uint8ArrayUtils.toPointer(args.key); | 
				
			||||||
 | 
					    final saltPtr = Uint8ArrayUtils.toPointer(args.salt); | 
				
			||||||
 | 
					//    final saltArray = allocate<Uint8>(count: args.salt.length); | 
				
			||||||
 | 
					//    final saltList = saltArray.asTypedList(args.length); | 
				
			||||||
 | 
					//    saltList.setAll(0, args.salt); | 
				
			||||||
 | 
					//    const memoryCost = 1 << 16; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//    _logger.fine('saltArray: ${ByteUtils.toHexList(saltArray.view)}'); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final result = argon2hash( | 
				
			||||||
 | 
					      keyPtr, | 
				
			||||||
 | 
					      args.key.length, | 
				
			||||||
 | 
					      saltPtr, | 
				
			||||||
 | 
					      args.salt.length, | 
				
			||||||
 | 
					      args.memory, | 
				
			||||||
 | 
					      args.iterations, | 
				
			||||||
 | 
					      args.parallelism, | 
				
			||||||
 | 
					      args.length, | 
				
			||||||
 | 
					      args.type, | 
				
			||||||
 | 
					      args.version, | 
				
			||||||
 | 
					    ); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    free(keyPtr); | 
				
			||||||
 | 
					    free(saltPtr); | 
				
			||||||
 | 
					//    free(saltArray); | 
				
			||||||
 | 
					    final resultString = Utf8.fromUtf8(result); | 
				
			||||||
 | 
					    return base64.decode(resultString); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override | 
				
			||||||
 | 
					  Future<Uint8List> argon2Async(Argon2Arguments args) async { | 
				
			||||||
 | 
					    return argon2(args); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Argon2FfiFlutter extends Argon2Base { | 
				
			||||||
 | 
					  Argon2FfiFlutter({this.resolveLibrary}) { | 
				
			||||||
 | 
					    final argon2lib = _loadLib(); | 
				
			||||||
 | 
					    _nativeAdd = argon2lib | 
				
			||||||
 | 
					        .lookup<NativeFunction<Int32 Function(Int32, Int32)>>('native_add') | 
				
			||||||
 | 
					        .asFunction(); | 
				
			||||||
 | 
					    argon2hash = argon2lib | 
				
			||||||
 | 
					        .lookup<NativeFunction<Argon2HashNative>>('hp_argon2_hash') | 
				
			||||||
 | 
					        .asFunction(); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static ResolveLibrary defaultResolveLibrary = (name) => name; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// forces loading of dynamic library on MacOS instead of assuming | 
				
			||||||
 | 
					  /// argon2 was statically linked. (ie. flutter usage, vs dart usage) | 
				
			||||||
 | 
					  static bool resolveLibraryForceDynamic = false; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final ResolveLibrary resolveLibrary; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int Function(int x, int y) _nativeAdd; | 
				
			||||||
 | 
					  @override | 
				
			||||||
 | 
					  Argon2Hash argon2hash; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int addIt(int x, int y) => _nativeAdd(x, y); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  DynamicLibrary _loadLib() { | 
				
			||||||
 | 
					    final resolveLibrary = this.resolveLibrary ?? defaultResolveLibrary; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!resolveLibraryForceDynamic && (Platform.isIOS || Platform.isMacOS)) { | 
				
			||||||
 | 
					      return DynamicLibrary.executable(); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    final libraryNames = [ | 
				
			||||||
 | 
					      [Platform.isAndroid, 'libargon2_ffi.so'], | 
				
			||||||
 | 
					      [Platform.isLinux, './libargon2_ffi_plugin.so'], | 
				
			||||||
 | 
					      [Platform.isWindows, 'argon2_ffi_plugin.dll'], | 
				
			||||||
 | 
					      [Platform.isMacOS, 'libargon2_ffi.dylib'], | 
				
			||||||
 | 
					      [Platform.isIOS, null], // only supports static linking. | 
				
			||||||
 | 
					    ]; | 
				
			||||||
 | 
					    final libraryName = libraryNames.firstWhere((element) => element[0] == true, | 
				
			||||||
 | 
					            orElse: () => throw StateError( | 
				
			||||||
 | 
					                'Unsupported Operating System ${Platform.operatingSystem}'))[1] | 
				
			||||||
 | 
					        as String; | 
				
			||||||
 | 
					    final path = resolveLibrary(libraryName); | 
				
			||||||
 | 
					    try { | 
				
			||||||
 | 
					      return DynamicLibrary.open(libraryName); | 
				
			||||||
 | 
					    } on ArgumentError catch (e, stackTrace) { | 
				
			||||||
 | 
					      _logger.severe( | 
				
			||||||
 | 
					          'Error while loading dynamic library from $path ($libraryName)', | 
				
			||||||
 | 
					          e, | 
				
			||||||
 | 
					          stackTrace); | 
				
			||||||
 | 
					      rethrow; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,15 @@ | 
				
			|||||||
 | 
					import 'dart:typed_data'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:argon2_ffi_base/src/argon2_ffi_base.dart'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Argon2FfiFlutter extends Argon2 { | 
				
			||||||
 | 
					  @override | 
				
			||||||
 | 
					  Uint8List argon2(Argon2Arguments args) { | 
				
			||||||
 | 
					    throw UnimplementedError(); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override | 
				
			||||||
 | 
					  Future<Uint8List> argon2Async(Argon2Arguments args) { | 
				
			||||||
 | 
					    throw UnimplementedError(); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,23 @@ | 
				
			|||||||
 | 
					// from https://github.com/hanabi1224/flutter_native_extensions/blob/master/src/compression/dart_native_compression/lib/utils/uint8_list_utils.dart | 
				
			||||||
 | 
					import 'dart:ffi'; | 
				
			||||||
 | 
					import 'dart:io'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'dart:typed_data'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:ffi/ffi.dart'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Uint8ArrayUtils { | 
				
			||||||
 | 
					  static Uint8List fromPointer(Pointer<Uint8> ptr, int length) { | 
				
			||||||
 | 
					    final view = ptr.asTypedList(length); | 
				
			||||||
 | 
					    final builder = BytesBuilder(copy: false); | 
				
			||||||
 | 
					    builder.add(view); | 
				
			||||||
 | 
					    return builder.takeBytes(); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static Pointer<Uint8> toPointer(Uint8List bytes) { | 
				
			||||||
 | 
					    final ptr = allocate<Uint8>(count: bytes.length); | 
				
			||||||
 | 
					    final byteList = ptr.asTypedList(bytes.length); | 
				
			||||||
 | 
					    byteList.setAll(0, bytes); | 
				
			||||||
 | 
					    return ptr.cast(); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue