2013-07-23 9 views
6

जब iOS पर gzip संकुचित डेटा को बढ़ाने के लिए कैसे पर खोजते समय, निम्न विधि परिणामों की संख्या में प्रकट होता है:क्या यह इस gzip inflate विधि में एक बग है?

- (NSData *)gzipInflate 
{ 
    if ([self length] == 0) return self; 

    unsigned full_length = [self length]; 
    unsigned half_length = [self length]/2; 

    NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length]; 
    BOOL done = NO; 
    int status; 

    z_stream strm; 
    strm.next_in = (Bytef *)[self bytes]; 
    strm.avail_in = [self length]; 
    strm.total_out = 0; 
    strm.zalloc = Z_NULL; 
    strm.zfree = Z_NULL; 

    if (inflateInit2(&strm, (15+32)) != Z_OK) return nil; 
    while (!done) 
    { 
     // Make sure we have enough room and reset the lengths. 
     if (strm.total_out >= [decompressed length]) 
      [decompressed increaseLengthBy: half_length]; 
     strm.next_out = [decompressed mutableBytes] + strm.total_out; 
     strm.avail_out = [decompressed length] - strm.total_out; 

     // Inflate another chunk. 
     status = inflate (&strm, Z_SYNC_FLUSH); 
     if (status == Z_STREAM_END) done = YES; 
     else if (status != Z_OK) break; 
    } 
    if (inflateEnd (&strm) != Z_OK) return nil; 

    // Set real length. 
    if (done) 
    { 
     [decompressed setLength: strm.total_out]; 
     return [NSData dataWithData: decompressed]; 
    } 
    else return nil; 
} 

लेकिन मैं डेटा के कुछ उदाहरण (पायथन के gzip module के साथ एक Linux मशीन पर हवा निकाल) का सामना करना पड़ा कि आईओएस पर चल रही यह विधि बढ़ने में विफल रही है। यहां क्या हो रहा है:

जबकि लूप inflate() रिटर्न के अंतिम पुनरावृत्ति में Z_BUF_ERROR और लूप निकल गया है। लेकिन inflateEnd(), जिसे लूप के बाद बुलाया जाता है, Z_OK देता है। कोड तब मानता है कि चूंकि() कभी भी Z_STREAM_END वापस नहीं आया, मुद्रास्फीति विफल रही और शून्य वापस आ गई।

इस पेज के अनुसार, http://www.zlib.net/zlib_faq.html#faq05 Z_BUF_ERROR एक गंभीर त्रुटि नहीं है, और सीमित उदाहरण के साथ मेरी परीक्षणों से पता चलता है कि डेटा को सफलतापूर्वक फुलाया अगर inflateEnd() रिटर्न Z_OK है, भले ही बढ़ के अंतिम कॉल() नहीं लौटाया Z_OK। ऐसा लगता है जैसे inflateEnd() डेटा के आखिरी हिस्से को भरने के लिए समाप्त हुआ।

मुझे संपीड़न और जीजीआईपी कैसे काम करता है, इसके बारे में बहुत कुछ नहीं पता, इसलिए मुझे यह समझने में संकोच नहीं है कि यह क्या करता है पूरी तरह से समझता है। मैं आशा करता हूं कि इस विषय के बारे में अधिक जानकारी वाले किसी को उपरोक्त कोड में इस संभावित तर्क दोष पर कुछ प्रकाश डाला जा सकता है, और इसे ठीक करने का एक तरीका सुझाता है। कि एक ही समस्या से ग्रस्त लगता है

एक अन्य विधि यह है कि गूगल अप बदल जाता है, यहां पाया जा सकता: https://github.com/nicklockwood/GZIP/blob/master/GZIP/NSData%2BGZIP.m

संपादित करें:

इसलिए, यह एक बग है! अब, हम इसे कैसे ठीक करें? नीचे मेरा प्रयास है। कोड समीक्षा, कोई भी?

- (NSData *)gzipInflate 
{ 
    if ([self length] == 0) return self; 

    unsigned full_length = [self length]; 
    unsigned half_length = [self length]/2; 

    NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length]; 
    int status; 

    z_stream strm; 
    strm.next_in = (Bytef *)[self bytes]; 
    strm.avail_in = [self length]; 
    strm.total_out = 0; 
    strm.zalloc = Z_NULL; 
    strm.zfree = Z_NULL; 

    if (inflateInit2(&strm, (15+32)) != Z_OK) return nil; 

    do 
    { 
     // Make sure we have enough room and reset the lengths. 
     if (strm.total_out >= [decompressed length]) 
      [decompressed increaseLengthBy: half_length]; 
     strm.next_out = [decompressed mutableBytes] + strm.total_out; 
     strm.avail_out = [decompressed length] - strm.total_out; 

     // Inflate another chunk. 
     status = inflate (&strm, Z_SYNC_FLUSH); 

     switch (status) { 
      case Z_NEED_DICT: 
       status = Z_DATA_ERROR;  /* and fall through */ 
      case Z_DATA_ERROR: 
      case Z_MEM_ERROR: 
      case Z_STREAM_ERROR: 
       (void)inflateEnd(&strm); 
       return nil; 
     } 
    } while (status != Z_STREAM_END); 

    (void)inflateEnd (&strm); 

    // Set real length. 
    if (status == Z_STREAM_END) 
    { 
     [decompressed setLength: strm.total_out]; 
     return [NSData dataWithData: decompressed]; 
    } 
    else return nil; 
} 

संपादित करें 2:

यहां एक नमूना Xcode प्रोजेक्ट कि इस मुद्दे को मैं में चल रहा हूँ दिखाता है Deflate सर्वर साइड पर होता है और डेटा बेस 64 और यूआरएल के माध्यम से ले जाया जा रहा से पहले इनकोडिंग है। एचटीटीपी। मैंने ViewController.m में यूआरएल एन्कोडेड बेस 64 स्ट्रिंग को एम्बेड किया है।

https://dl.dropboxusercontent.com/u/38893107/gzip/binary.zip

यह वह जगह है: यूआरएल-डिकोड और बेस 64-डिकोड और साथ ही अपने gzipInflate तरीकों

https://dl.dropboxusercontent.com/u/38893107/gzip/GZIPTEST.zip

यहाँ बाइनरी फ़ाइल के रूप में अजगर gzip पुस्तकालय द्वारा हवा निकाल है NSDataExtension.m में हैं URL ने बेस 64 स्ट्रिंग को एन्कोड किया जो HTTP पर ले जाया जाता है: https://dl.dropboxusercontent.com/u/38893107/gzip/urlEncodedBase64.txt

+0

यदि जीजीआईपी स्ट्रीम पूर्ण नहीं होता है तो प्रयास अनंत लूप में जाता है। –

+0

वैसे, "binary.zip" एक ज़िप फ़ाइल नहीं है। यह एक gzip फ़ाइल है। नाम "binary.gz" होना चाहिए। –

+0

यूआरएल binary.zip (जिसे binary.gz कहा जाना चाहिए) को डीकोड करता है, और मेरे उत्तर में प्रदान किया गया कोड ठीक से 221213 बाइट टेक्स्ट फ़ाइल में डिकंप्रेस करता है। मैंने यह देखने के लिए आपके कोड को नहीं देखा कि क्या गलत है - यह आपका काम है। –

उत्तर

6

हाँ, यह एक बग है।

यह वास्तव में सही है कि inflate()Z_STREAM_END वापस नहीं करता है, तो आपने मुद्रास्फीति पूरी नहीं की है। inflateEnd()Z_OK लौटने का वास्तव में बहुत मतलब नहीं है - बस यह एक वैध राज्य दिया गया था और स्मृति को मुक्त करने में सक्षम था।

तो inflate() सफलतापूर्वक घोषित करने से पहले Z_STREAM_END वापस कर देना चाहिए। हालांकि Z_BUF_ERROR छोड़ने का कोई कारण नहीं है। उस स्थिति में आप अधिक इनपुट या अधिक आउटपुट स्पेस के साथ फिर से inflate() पर कॉल करें। फिर आपको Z_STREAM_END मिल जाएगा।

प्रलेखन से zlib.h में:

/* ... 
Z_BUF_ERROR if no progress is possible or if there was not enough room in the 
output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and 
inflate() can be called again with more input and more output space to 
continue decompressing. 
... */ 

अद्यतन:

के बाद से वहाँ गाड़ी कोड वहाँ के आसपास चल, नीचे वांछित विधि लागू करने के लिए उचित कोड है। यह कोड अधूरा gzip धाराओं, concatenated gzip धाराओं, और बहुत बड़ी gzip धाराओं को संभालती है। बहुत बड़ी gzip धाराओं के लिए, unsignedz_stream में लंबाई 64-बिट निष्पादन योग्य के रूप में संकलित होने पर पर्याप्त नहीं है। NSUInteger 64 बिट्स है, जबकि unsigned 32 बिट्स है। उस स्थिति में, आपको इसे inflate() पर फ़ीड करने के लिए इनपुट पर लूप करना होगा।

यह उदाहरण किसी भी त्रुटि पर nil देता है। त्रुटि की प्रकृति प्रत्येक return nil; के बाद एक टिप्पणी में नोट की जाती है, यदि अधिक परिष्कृत त्रुटि हैंडलिंग वांछित है।

- (NSData *) gzipInflate 
{ 
    z_stream strm; 

    // Initialize input 
    strm.next_in = (Bytef *)[self bytes]; 
    NSUInteger left = [self length];  // input left to decompress 
    if (left == 0) 
     return nil;       // incomplete gzip stream 

    // Create starting space for output (guess double the input size, will grow 
    // if needed -- in an extreme case, could end up needing more than 1000 
    // times the input size) 
    NSUInteger space = left << 1; 
    if (space < left) 
     space = NSUIntegerMax; 
    NSMutableData *decompressed = [NSMutableData dataWithLength: space]; 
    space = [decompressed length]; 

    // Initialize output 
    strm.next_out = (Bytef *)[decompressed mutableBytes]; 
    NSUInteger have = 0;     // output generated so far 

    // Set up for gzip decoding 
    strm.avail_in = 0; 
    strm.zalloc = Z_NULL; 
    strm.zfree = Z_NULL; 
    strm.opaque = Z_NULL; 
    int status = inflateInit2(&strm, (15+16)); 
    if (status != Z_OK) 
     return nil;       // out of memory 

    // Decompress all of self 
    do { 
     // Allow for concatenated gzip streams (per RFC 1952) 
     if (status == Z_STREAM_END) 
      (void)inflateReset(&strm); 

     // Provide input for inflate 
     if (strm.avail_in == 0) { 
      strm.avail_in = left > UINT_MAX ? UINT_MAX : (unsigned)left; 
      left -= strm.avail_in; 
     } 

     // Decompress the available input 
     do { 
      // Allocate more output space if none left 
      if (space == have) { 
       // Double space, handle overflow 
       space <<= 1; 
       if (space < have) { 
        space = NSUIntegerMax; 
        if (space == have) { 
         // space was already maxed out! 
         (void)inflateEnd(&strm); 
         return nil;   // output exceeds integer size 
        } 
       } 

       // Increase space 
       [decompressed setLength: space]; 
       space = [decompressed length]; 

       // Update output pointer (might have moved) 
       strm.next_out = (Bytef *)[decompressed mutableBytes] + have; 
      } 

      // Provide output space for inflate 
      strm.avail_out = space - have > UINT_MAX ? UINT_MAX : 
          (unsigned)(space - have); 
      have += strm.avail_out; 

      // Inflate and update the decompressed size 
      status = inflate (&strm, Z_SYNC_FLUSH); 
      have -= strm.avail_out; 

      // Bail out if any errors 
      if (status != Z_OK && status != Z_BUF_ERROR && 
       status != Z_STREAM_END) { 
       (void)inflateEnd(&strm); 
       return nil;     // invalid gzip stream 
      } 

      // Repeat until all output is generated from provided input (note 
      // that even if strm.avail_in is zero, there may still be pending 
      // output -- we're not done until the output buffer isn't filled) 
     } while (strm.avail_out == 0); 

     // Continue until all input consumed 
    } while (left || strm.avail_in); 

    // Free the memory allocated by inflateInit2() 
    (void)inflateEnd(&strm); 

    // Verify that the input is a valid gzip stream 
    if (status != Z_STREAM_END) 
     return nil;       // incomplete gzip stream 

    // Set the actual length and return the decompressed data 
    [decompressed setLength: have]; 
    return decompressed; 
} 
+0

धन्यवाद मार्क। खुद zlib लेखक से प्रतिक्रिया की तरह कुछ भी नहीं! जब तक Z_STREAM_END वापस नहीं आ जाता है तब तक मैंने लूप को ठीक करके बग को ठीक करने का प्रयास किया है (रुचि रखते हुए ऊपर संपादित प्रश्न देखें)। लेकिन एनोसिएटेड उदाहरण @ जोआचिम लिंक से जुड़ा हुआ है, आंतरिक लूप strm.avail_out == 0 पर सशर्त है, जिसे मैं पीछे कारण समझ नहीं पा रहा हूं। –

+0

आपका फिक्स हमेशा काम नहीं करता है। विशेष रूप से, अगर एक अपूर्ण gzip स्ट्रीम खिलाया जाता है तो यह एक अनंत लूप में जाएगा। इसके अलावा सामान्य रूप से यह दृष्टिकोण तभी काम करेगा जब संपीड़ित और असम्पीडित लंबाई एक हस्ताक्षरित प्रकार में फिट होने के लिए पर्याप्त छोटी हो। और केवल तभी जब कुल लंबाई एक हस्ताक्षरित लंबे प्रकार में फिट होगी। उस विधि को लिखने के लिए और अधिक मजबूत तरीके हैं जो उन धारणाओं पर निर्भर नहीं हैं। मैं अपने उत्तर में सही कोड जोड़ूंगा। –

+0

strm.avail_out == 0 पर लूपिंग, या समकक्ष, strm.avail_out के लिए प्रतीक्षा! = 0, प्रदत्त संपीड़ित इनपुट से उत्पन्न किए जा सकने वाले सभी संपीड़ित डेटा की प्रतीक्षा करता है। ऐसा तब तक नहीं किया जाता जब तक कि यह आउटपुट बफर को भरता नहीं है। थोड़ा संकुचित डेटा कभी-कभी असम्पीडित डेटा उत्पन्न कर सकता है, इसलिए आपको इसे बाहर खींचने के लिए एक लूप की आवश्यकता होती है। –

2

हाँ, एक बग की तरह दिखता है। this annotated example from the zlib site के अनुसार, Z_BUF_ERROR केवल एक संकेत है कि अब तक कोई आउटपुट नहीं है जब तक कि अधिक इनपुट के साथ inflate() प्रदान नहीं किया जाता है, न कि खुद को फुफ्फुस लूप को असामान्य रूप से निरस्त करने का कारण नहीं है।

वास्तव में, लिंक किया गया नमूना Z_BUF_ERROR को Z_OK जैसा संभालता प्रतीत होता है।

+0

धन्यवाद! मैंने अपने प्रयास किए गए फिक्स के साथ प्रश्न संपादित किया है। –

+0

@ व्यक्तिपरक-सी जब तक आप एक ही समय में सभी इनपुट डेटा पास करते हैं, मुझे कोई समस्या नहीं दिखाई दे रही है। नमूना में बाहरी लूप "स्ट्रीमिंग" के लिए है, यानी अगर avail_out 0 है, तो यह इनपुट बफर को फिर से भरने का प्रयास करता है और 'Z_STREAM_END' लौटाए जाने तक फिर से प्रयास करता है। चूंकि आपने एक ही समय में सभी डेटा पास कर दिए हैं और फिर से भरने के लिए कुछ भी नहीं है, इसलिए मैं नहीं देख सकता कि आपके पास कोई विकल्प नहीं है, लेकिन जब तक आपको कोई कठिनाई न हो या 'Z_STREAM_END' प्राप्त न हो जाए। –

+0

इसकी समीक्षा के लिए धन्यवाद। –

संबंधित मुद्दे