FidonetサイトのNode.js:JAM形式で保存されたエコーメールのヘッダーをJavaScriptで読み取ります

今日、私は鍵を検討する2つの理由があります。



まず、先週jParserのドキュメントを翻訳した後(jParserを使用してBMPファイルを分析する RReverserの例を読んだ後)、次のステップに進むのが適切だと思います :トピックを開発し、読者と自分の例を共有してくださいjParserを使用して、やや複雑なデータ構造を分析します。 (一部、これはalekciyがjParserの実際の使用のさらなる例に興味を持って尋ね 質問に答えます。)



第二に、約6か月前 2011年11月26日ertaquoが、FidonetでNode.jsを使用したい理由を尋ねました。 それから私は名前が好きだと言いました(明確にせずにロシアのコンピューター世界で「ノード」 または「ノード」という用語が明確に使用されていた場合、デフォルトではフィドネットノードを意味していたことを覚えています)、私は作業コードの明確な例を与えることができませんでした今すぐ持っていきます



したがって、例は二重になります。 JAM形式で保存されたFidonet電子メールメッセージのヘッダーの分析に注目します。 この形式は、遠い昔からフィドネットで人気がありました ウィキペディアによると、JAMの登場は1993年に遡ります)。 私はすぐに別の一般的な形式 Squish )よりもJAMを好んでいことを言わなければなりません。この最後の1つはメッセージヘッダーに9個以下の応答の識別子を格納するのに対し、JAMは制限された長さの配列( リンクリスト )ではなく、より柔軟なデータ構造を使用するためです最も活発で分断されたディスカッションでも、完全な回答ツリーを構築できます。



JAMのドキュメントはさまざまなBBSファイドで簡単に見つけることができますが、BBSは時間の経過とともに住所を閉鎖または変更する傾向があるため、信頼性のために、5年前に自分の手紙を参照します。 (チェコのBBSは、その後私のソースとして使用されていましたが、すでに閉鎖されています。この世界のすべてが荒れ狂っています。)



ご覧のとおり、Fidonetの電子メールメッセージのヘッダーはJHRファイル内に保存されています。 このファイルは、固定長ヘッダー( FixedHeaderInfoStruct )で構成され、その後に実際のメッセージヘッダー( MessageHeader )が続きます。各ヘッダーは、再び固定サイズ構造( MessageFixedHeader )と、いくつかのフィールド( SubFieldXX )これは、 MessageFixedHeader構造内のSubfieldLenフィールドで設定されます。 SubFieldXXフィールドは、固定サイズのヘッダーと、それに続くバイトの文字列で構成されます。バイトの長さは、前のdatlen numberで指定されています。 (これは、同じ90年代に共通のPascal方言での文字列の実装を思い起こさせます-Turbo -Pascal、 UCSD Pascal。ただし、Pascalでは、長さは1バイトで示され、JAMでは、 datlenの数はulong です 。つまり、32ビットです。これは賢明です。 )



もう1つの重要な状況はあまり目立ちません。JHR ファイル内では、 MessageHeaderヘッダーは必ずしもエンドツーエンドに従うとは限りません。 「メッセージヘッダーの更新」サブセクションは、レターを編集または処理した後、ヘッダーのボリュームが大きくなり、ファイルの最後に配置され、前のヘッダーが削除済みとしてマークされることを示します。 見出しが増えていないが減少している文字の運命についてはも言われていないが、実際には、多くのFidonetプログラムは以前の見出しに新しい見出しを書き込み、それに応じてSubfieldLen値(および必要に応じて個々のdatlen値)を変更します。 これと後続のMessageHeaderの間には、直前の最後のSubFieldXXフィールドの内容で構成されるゴミがあります。 そのため、次のMessageHeaderヘッダーを読み取った後、次のMessageHeaderヘッダーに移動する合理的な方法はありません。ただし、3つの「JAM」 ASCII文字の後にヌルバイトが続く文字列を見つけることです。これは、 MessageFixedHeaderヘッダーを開始する署名シーケンスです。



したがって、JHRファイルからRAMにエコーメールヘッダーを読み取るNode.jsのモジュールコードは、次のように概説できます。



var fs = require('fs'); var jParser = require('jParser'); var ulong = 'uint32'; var ushort = 'uint16'; var JAM = function(echotag){ if (!(this instanceof JAM)) return new JAM(echotag); this.echotag = echotag; // Buffers: this.JHR = null; /* this.JDT = null; this.JDX = null; this.JLR = null; */ } JAM.prototype.readJHR = function(callback){ // (err) if (this.JHR !== null) callback(null); fs.readFile(this.echotag+'.JHR', function (err, data) { if (err) callback(err); this.JHR = data; callback(null); }); } JAM.prototype.ReadHeaders = function(callback){ // err, struct this.readJHR(function(err){ if (err) callback(err); var thisJAM = this; var parser = new jParser(this.JHR, { 'reserved1000uchar': function(){ this.skip(1000); return true; }, 'JAM0' : ['string', 4], 'FixedHeaderInfoStruct': { 'Signature': 'JAM0', 'datecreated': ulong, 'modcounter': ulong, 'activemsgs': ulong, 'passwordcrc': ulong, 'basemsgnum': ulong, 'RESERVED': 'reserved1000uchar', }, 'SubField': { 'LoID': ushort, 'HiID': ushort, 'datlen': ulong, 'Buffer': ['string', function(){ return this.current.datlen }] /* 'type': function(){ switch( this.current.LoID ){ case 0: return 'OADDRESS'; break; case 1: return 'DADDRESS'; break; case 2: return 'SENDERNAME'; break; case 3: return 'RECEIVERNAME'; break; case 4: return 'MSGID'; break; case 5: return 'REPLYID'; break; case 6: return 'SUBJECT'; break; case 7: return 'PID'; break; case 8: return 'TRACE'; break; case 9: return 'ENCLOSEDFILE'; break; case 10: return 'ENCLOSEDFILEWALIAS'; break; case 11: return 'ENCLOSEDFREQ'; break; case 12: return 'ENCLOSEDFILEWCARD'; break; case 13: return 'ENCLOSEDINDIRECTFILE'; break; case 1000: return 'EMBINDAT'; break; case 2000: return 'FTSKLUDGE'; break; case 2001: return 'SEENBY2D'; break; case 2002: return 'PATH2D'; break; case 2003: return 'FLAGS'; break; case 2004: return 'TZUTCINFO'; break; default: return 'UNKNOWN'; break; } } */ }, 'MessageHeader': { 'Signature': 'JAM0', 'Revision': ushort, 'ReservedWord': ushort, 'SubfieldLen': ulong, 'TimesRead': ulong, 'MSGIDcrc': ulong, 'REPLYcrc': ulong, 'ReplyTo': ulong, 'Reply1st': ulong, 'Replynext': ulong, 'DateWritten': ulong, 'DateReceived': ulong, 'DateProcessed': ulong, 'MessageNumber': ulong, 'Attribute': ulong, 'Attribute2': ulong, 'Offset': ulong, 'TxtLen': ulong, 'PasswordCRC': ulong, 'Cost': ulong, 'Subfields': ['string', function(){ return this.current.SubfieldLen; } ], /* 'Subfields': function(){ var final = this.tell() + this.current.SubfieldLen; var sfArray = []; while (this.tell() < final) { sfArray.push( this.parse('SubField') ); } return sfArray; }, */ 'AfterSubfields': function(){ var initial = this.tell(); var bytesLeft = thisJAM.JHR.length - initial - 4; var seekJump = 0; var sigFound = false; var raw = this; if (bytesLeft <= 0) return 0; do { this.seek(initial + seekJump, function(){ var moveSIG = raw.parse('JAM0'); if (moveSIG === 'JAM\0') { sigFound = true; /* if (seekJump > 0){ console.log( 'initial = ' + initial + ', seekJump = ' + seekJump + ', moveSIG = ' + moveSIG ); } */ } }); seekJump++; } while (!sigFound && (seekJump < bytesLeft) ); this.skip(seekJump-1); return seekJump-1; } }, 'JHR': { 'FixedHeader': 'FixedHeaderInfoStruct', 'MessageHeaders': function(){ var mhArray = []; while (this.tell() < thisJAM.JHR.length - 69) { mhArray.push( this.parse('MessageHeader') ); } return mhArray; } } }); callback(null, parser.parse('JHR')); }); } module.exports = JAM;
      
      





このスケッチは、エクスポートされたJAMオブジェクト( JHRフィールド内内のJHRファイルからの生データのキャッシングを使用します -現在のモジュール設計の観点からは不経済なソリューションですが、 ReadHeadersメソッドとともに、たとえば、 FixedHeaderInfoStructヘッダーのみ。 フィールドは、JAM形式(JDT、およびJDX、およびJLR)の他の3つのファイルにも提供されますが、コメント化されています。 (理想的には、キャッシュの関連性に注意を払う必要があります-stat ()実行するか、 watchFile ()をまったく実行しません-しかし、このコードがモジュールなしでモジュールの初期ドラフトで機能することは明らかです。)



JAMドキュメントのデータ型(たとえばulong )は jParserによって設定されてません(たとえば、 '' ulong ':' uint32 ' )が、 JavaScript変数として宣言されています(たとえば、 var ulong =' uint32 ' )、その値はデータ構造の説明。 これは速度のためです。V8javascriptエンジンのコードはjParserモジュールのコードよりもはるかに高速に動作することは明らかです。



SubField構造体の説明には、コメント化されたタイプフィールドがあります。このフィールドには、JAMドキュメントから借用したニーモニックフィールド指定を含むjavascript関数が入力されています。 デバッグ目的で使用できます。



MessageHeader構造内のSubfieldsフィールドは、2つの方法で定義されます。 最初の(高速)は、このフィールドをサイズSubfieldLenのバイト文字列として読み取ります。 2番目の(コメントアウト)は、このフィールドを完全に処理し、jParserを使用してサブフィールドを分離します。モジュールを使用するアプリケーションがフィードのヘッダーの可変部分のメタデータを必要とする場合、長いボックスで分析を延期します。



AfterSubfieldsフィールドには、3バイトのASCII文字 「JAM」とそれに続くゼロバイトの文字列の単純な検索が含まれます。この理由は、前の段落の1つで説明されています。 console.log()へのコメントアウトされた呼び出しには、デバッグの意味があります。 (内部変数moveSIGの名前は、「 すべてのベースは私たちのものです」というミームを暗示しています 。)



JHR構造体のMessageHeadersフィールドの説明の数字69は「魔法」です。 その目標は、分析がファイルの終わりに近づきすぎないようにすることです。ファイルの終わりでは、ガベージデータも予想されます。



このテストスクリプトを使用して分析の速度を確認しました。



 var JAM = require('../'); var util = require('util'); console.log( new Date().toLocaleString() ); var blog = JAM('blog-MtW'); blog.ReadHeaders(function(err,data){ if (err) throw err; //console.log( util.inspect(data, false, Infinity, false) ); console.log( new Date().toLocaleString() ); });
      
      





スクリプトはテストサブディレクトリにあるため、最初の行では親ディレクトリを使用しますメインディレクトリのテキストはindex.jsファイルにあります Node.jsではこの名前がデフォルトで暗示されているため、親ディレクトリのみを指定するだけで十分です。



blog-MtW.jhrファイルのテストデータには、2007年3月以降に蓄積された私のFidonetブログブログエコーRu.Blog.Mithgolブログエントリが含まれています



シングルコアPentium IV(2.2 GHz)でテストを実行すると、ヘッダーが3〜4秒で処理されることがわかります。 Subfields配列の単純な読み取り値がその分析(現在はコメントアウトされています)に置き換えられた場合、この時間はまだ2倍になります。



Fidonetサイトでは、このようなエコー会議が100を超えることがあり、電子メールヘッダーの合計分析時間は数分になるため、これは1つのエコー会議にとっては多くのことです。



しかし、fidotniksはおそらく、人気のあるGoldEDメールエディターGoldED(GoldED +、 GoldED-NSF)がエコーカンファレンス(作業の開始時)をはるかに速くスキャンし、スプラッシュスクリーンのステータスバーで名前が点滅して見やすいことを思い出す必要はないでしょう。ほんの数秒が費やされ、それ以上はありません。 意図せずに不快な結論に達しなければなりません。バイナリデータのJavascript分析は、高速V8エンジンでも、1桁以上遅く、さらに1桁以上遅くなることはありません。



作業の開始時のGoldEDがファイル全体を高速化するのではなく、1つのヘッダー構造FixedHeaderInfoStructのみを疑うのは皮肉なままです(エコー会議のメッセージ数を表示するにはデータが十分であり、作業の開始時にはGoldED 何もしませ)-真実、 CVS GoldED +を理解する時間がなかったので、この疑いを確認も否定もできません。



私のモジュール(JAMヘッダーのリーダー )のコードを無料のMITライセンスの下でGithubに投稿しました。



All Articles